Performance

One of the most important aspects of interfacing with native code is to make sure that you are passing data efficiently. Passing primitive types to native functions is a perfect solution for a few and fixed number of parameters. Primitive arrays can be viable when passing more than a few or a variable number of parameters. They are also useful when you need to return multiple values. Passing the data as Java objects does have some disadvantages, primarily due to the overhead of accessing the objects from native code. Reading from and writing to a Java object from the native side means that JNI functions have to be used as accessors to set and retrieve data by value. Direct buffers are by far the best approach for passing or sharing a large amount of data between the two worlds. They are especially important when dealing with large chunks of data and can pay off even for a small amount of data.

Mixing native code and calling native functions from the Java side can have reasonable effects on the performance of your game. Table 11.6 shows the timing comparison of native and nonnative methods running with different VM settings. To time the overhead of calling a native function, a test was set up where the Main method called an update method and its equivalent nativeUpdate(). The bodies of the methods simply incremented a counter. Each method was called 100,000,000 times after the VM had the chance to warm up. The settings include the default VM settings that allow the HotSpot to compile the Java code to native CPU instructions, as well as inline qualified methods. The column labeled No Inlining indicates that the VM was also not allowed to perform any inlining by specifying the command-line option -XX:MaxInlineSize=0. Last, the tests were also performed in fully interpreted mode (-Xint), which is similar to the classic Java VMs. The numbers in the table have been normalized using the timing of the update method when running in interpreted mode.

There are a couple of points to note. Intuitively, the default setting resulted in a far better performance than the interpreted mode. This is because the VM was able to inline the method call and then compile the byte code to native CPU instructions. As expected, without inlining, the JIT compiler resulted in reasonable performance increase. On the other hand, the timings of nativeUpdate() are roughly the same, regardless of the VM settings. This is because a native function is not inlined and the resulting bytecode remains unoptimized. The data presented in the table indicates that the pure overhead of calling native functions is only about 10 percent.

Calling Java methods and accessing object fields from native code does have some overhead as well. The overhead is due mostly to using JNI functions as assessors, which results in an extra level of indirection. Note that these overheads are relatively trivial. The most important point to realize is that the more expensive the task performed by a function, the less noticeable the overhead will be. For example, if you call a render function that takes up the most significant chunk of time in your game, the overhead will be imperceptible. The overhead may even be too small to measure for some applications.



Practical Java Game Programming
Practical Java Game Programming (Charles River Media Game Development)
ISBN: 1584503262
EAN: 2147483647
Year: 2003
Pages: 171

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net