| 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.
 |