Integrating low-level software capabilities within Java applications
Since its release to the public in 1995, the Java programming language has quickly risen to a position of dominance within the software development community. Among the reasons for its rapid acceptance are twofold improvements in software developer productivity, up to tenfold improvements in the ease of software reuse, greater availability of off-the-shelf reusable software functionality, and superior dynamic behavior. This combination of technical advances has allowed companies that use Java to develop software systems with significantly enhanced functionality and far fewer bugs than previous industry norms while maintaining reduced budgets and accelerating product development schedules.
Though many embedded software engineers view Java as a language primarily suited for implementing graphical user interfaces, the language’s strengths are relevant to any embedded application that involves complexity, large size, evolving requirements, multiple teams of developers, or independently developed, off-the-shelf software components. When applied to projects with these constraints, developers using Java typically see a twofold improvement in productivity when creating new functionality and a five to tenfold reduction in software maintenance and integration costs compared to the results achieved using C or C++.
Meeting low-level software needs
Embedded device developers’ need to address low-level concerns outside the realm of traditional Java capabilities sometimes inhibits Java use in embedded devices. Examples of low-level software include vector drawing libraries for an in-vehicle navigation system, encoders and decoders for Software-Defined Radios within multiband cell phones, and device drivers for sensors and actuators in a factory floor automation system.
The Java Specification Request (JSR) 302 expert group is developing a low-level variant of Java to address the needs of safety-critical development. This low-level Java technology offers throughput, memory footprint, and predictable response latencies within 15 percent of optimized C. Besides supporting safety-critical applications such as commercial avionics, medical instrumentation, and railway control, JSR 302 is also relevant to less-critical, low-level, real-time applications such as video capture and playback, in-vehicle navigation software, and control plane infrastructure for telecommunications equipment. The specification provides mechanisms to allow portable Java implementation of device drivers and interrupt handlers. It also serves as an efficient and robust integration layer to connect high-level Java application software with lower-level software written in other languages.
For low-level code, developers are expected to carefully analyze memory and CPU time requirements to assure reliable operation and compliance with all hard real-time deadlines. The low-level Java technology is much smaller and simpler than traditional Java implementations partly because it does not rely on tracing garbage collection, instead satisfying all temporary object allocations from the runtime stack. This simpler runtime model allows developers to address very low-level requirements including device drivers and interrupt handlers.
Matching C performance
Unlike stack memory allocation in legacy languages such as C and C++, stack allocation in Java is proven safe by an enhanced byte-code verification tool that enforces the absence of dangling pointers and memory leaks. The Aonix PERC Pico toolchain illustrated in Figure 1 is based on the ideas of the not-yet-completed JSR 302 specification. The PERC Pico verifier ensures that code conforms to the Java subset compatible with hard real-time deployment. Once an application program proves consistent with these subset requirements, the PERC Pico translator converts the program into stylized C code.
This approach preserves the portability, modularity, and abstraction benefits of Java while taking advantage of the efficiency and simplicity of C. Note that scope-based allocation in PERC Pico-generated C is much more efficient and deterministic than the malloc and free services typically invoked from handwritten C.
Table 1 compares the efficiency of optimized C with commercial products supporting soft real-time (PERC Ultra) and hard real-time (PERC Pico) Java execution. The measured workload was patterned after a real-world defense system application that a customer used to evaluate the comparative performance of C and Java. Though the Sun HotSpot Virtual Machine is not designed to support real-time operation, developers evaluated the benchmark on Sun HotSpot as well.
Interestingly, the average execution time on Sun HotSpot is even better than optimized C at 294 ns. However, the maximum time for the HotSpot implementation was 8,089 ns and the standard deviation was 228.8 ns. Also noteworthy was HotSpot’s virtual memory consumption of more than 250 MB. HotSpot improves upon C speed by profiling the code as it runs and in-lining the implementation of certain key methods to optimize their execution on-the-fly. The reason for the very high standard deviation is that HotSpot’s dynamic adaptive JIT compiler introduces unpredictable delays in application code execution.
With the ability of hard real-time Java to match C performance, the motivation to use C for implementing low-level, performance-critical code decreases significantly. Today, most successfully deployed real-time Java applications incorporate a combination of Java and C code integrated using the Java Native Interface (JNI). The C code is required to implement portions of the system that demand higher throughput, more determinism, or smaller (more economical) memory footprint than is feasible with traditional Java. In some cases, C use for low-level software is motivated by a need to directly interface to hardware devices. Multiple developers have identified that the JNI interface between Java and C is the single most common source of errors in their developed systems.
Compared to writing low-level code in C and combining this with Java and the JNI protocol, the alternative of using Java for low-level functionality offers improved robustness and scalability because object-oriented protocols are enforced by byte-code verification on both sides of the interface. This also enhances performance because the byte-code verification performed on both sides of the interface enables in-lining and other optimizations not possible with JNI.
The full Java solution
Aonix recently experimented with a Mandelbrot fractal image application. For each pixel of the Mandelbrot image, low-level code calculated the pixel color. Traditional Java was used to render the image. This test compared the efficiency of using low-level Java code versus optimized C to implement the low-level code.
Measurements reveal that C runs slightly faster than low-level Java. However, the cost of invoking C from traditional Java is much greater than the cost of invoking low-level Java code. The full Java solution consistently outperformed the Java/C implementation. For certain images, the total rendering time was more than 2.3x faster with the full Java solution.
These results indicate that traditional Java is not well suited to low-level development needs. However, newer Java technologies offer throughput, latency, and memory footprint comparable with C. Using Java for both high- and low-level capabilities yields superior performance, fewer bugs, and less expensive ongoing maintenance.
Aonix kelvin@aonix.com 520-991-6727 www.aonix.com
