Java set to pour out potent solutions
By George Shaw -President -Shaw Laboratories Ltd. Hayward, Calif.
Set-top boxes (STB) are built on a variety of hardware platforms. While there is much speculation on just what capabilities new STB designs should have, manufacturers recognize that having an adaptable, programmable STB can greatly reduce the risk involved in placing millions of these new devices in homes. Thus, problems of program portability and security are paramount. With millions of cable subscribers, dozens of STB designs are in service at any moment. The desired programming must run on all of them and must not cause them to crash The Java programming language was created to solve just these problems. Java is a language very similar to C++, but without the guns, knives, or clubs. Removed are pointers, "typedefs," unions, structures, functions, automatic type coercion, multiple inheritance, operator overloading, and "goto" statements. Added are garbage collection, interfaces similar to protocols in Objective C, execution threads, persistent objects, and network support.
These changes eliminate those elements of C++ that produce the most bugs, make the system most vulnerable, and are the most misunderstood and least used. They also add support within the language for operating-system features to enhance application portability.
Byte-code distribution To enable Java programs to run on different platforms, they are distributed in a form known as byte-codes. The byte-codes are instruction opcodes for a hypothetical Java computer called the Java virtual machine (VM), on which the programs will run. The Java VM is a stack-based computer architecture that is typically emulated on real computers to execute a Java language program. Java programs are compiled for the Java VM in the same manner as C++ programs are for real computers, except that the output of the Java compiler is targeted for running on a Java VM.
Before a Java program is executed, several events occur to ensure program execution is robust and secure. First, the Java byte-code program is loaded by the class loader, along with any inherited or referenced classes that the code will need. Note that, unlike many languages, most address references in Java programs have not been resolved at compile-link time. This reduces the dependence of the Java program on the system memory layout and enhances security by preventing any address assumptions from being made. It also allows individual methods to be replaced without recompiling the entire program, thus easing maintenance and allowing greater reuse of classes.
Next, the byte-codes are checked by the byte-code verifier. The verifier ensures that a valid Java program exists, and that the program is not hostile and thus will not violate system integrity. Since the class loader has made the program pieces available, the memory layout for the total executable can be determined. During verification, a new copy of the byte-codes is created that has most addresses resolved. Addresses that cannot be resolved during verification are resolved during initial execution.
Note that the entire program is not necessarily loaded and verified at once. Object classes need not be downloaded and verified until they are referenced at run time. Thus, parts of an application that are not used need not be loaded or verified. This reduces the initial load time and begins application execution sooner, though newly executed parts of the program will execute more slowly the first time because they will need to be downloaded and verified.
The next step is determined by the type of execution: whether the byte-codes will be interpreted, or whether the byte-codes will be compiled for the host computer, referred to as just-in-time (JIT) compilation.
Interpreting Java
Most Java byte-codes are fairly straightforward to execute, but several represent higher-level operations (such as "create new object") that require a subroutine call. Thus, interpreting byte-codes to emulate the Java virtual machine is similarly direct. For a Java interpreter written in C, the byte-codes are simply executed through a switch statement. A good C compiler will compile this into instructions similar to those produced by hand-coded assembler.
Interpreting consists of obtaining a byte-code, shifting it left to create a table offset, adding the offset to the base address of a dispatch table, loading the address of the code fragment to interpret the byte-code from the table, and/or then branching to the code fragment. This process is commonly known as token threading. The process is simple, but it requires about three instructions and five memory references for every Java byte-code executed.
Once the code fragment is reached, it must perform the action of the byte-code as it would execute on Java's VM. Most general-purpose processors-Sparc, 80X86, 680X0, PowerPC, ARM and MIPS-require at least seven conventional processor instructions when using a C source-code implementation to interpret and execute an integer add (IADD) on the Java VM.
To improve performance, Java allows programs to be translated from byte-codes to instructions for the host processor. For best performance, most of the equivalent of the back end of a C++ compiler is required-at least 50 kbytes to 100 kbytes. Not only does this consume a sizable chunk of memory, but it could also consume considerable time because this process must occur every time the Java byte-codes are loaded. Therefore, this level of optimization is impractical on anything smaller than a desktop computer. While it could be argued that the optimization would run in the background while data is downloaded via modem, this argument fails as modem speeds increase.
While fully optimized JIT compiled code appears impractical, a less aggressive optimization is not. Just translating each byte-code to in-line machine code will eliminate the overhead of the byte-code interpreter, albeit typically at the cost of several times the memory. Some other easy optimizations, such as keeping the top of stack in a register, might also be performed. This results in a significant speed improvement over interpreted code.
The less aggressive JIT compilation improves performance by several times, but each CPU still typically executes at least twice as many instructions and increases code size about five times over the original Java byte-code. The IADD instruction alone is, of course, a poor benchmark, but industry reports indicate the results are representative. Thus, even though DRAM is relatively inexpensive, a factor-of-five increase in memory requirements and cost is very significant.
More compatible
Given the above memory and time costs, the best performance would seem to be obtained by using a more compatible host-computer architecture, that is, by executing on a less memory-intensive stack-based processor such as Patriot Scientific's ShBoom PSC1000, which in many respects matches the Java VM architecture. The PSC1000 can be used in embedded applications such as STBs where cost and space are at a premium.
Efficient JIT compilation for a stack-based processor is a comparably trivial matter. It consists, during the byte-code verification process, of mapping Java byte-codes to processor opcodes and resolving compiled addresses. For the IADD instruction, the PSC1000 executes just one byte-sized opcode. It executes the same or fewer bytes of opcodes than the equivalent Java byte-codes for 38 percent of the Java VM instructions. Eleven percent require one additional opcode byte. Forty percent require two additional opcode bytes, or are complex operations that require a subroutine call, as they would on most embedded processors. The remaining 10 percent require six or fewer additional opcode bytes. While these numbers represent small code size, they also represent a similarly reduced number of instructions required to be executed.
Copyright * 1996 CMP Publications, Inc. |