神刀安全网

Java vs C app performance – Gary explains

Java is the official programming language of Android and it is the basis for many components of the OS itself, plus it is found at the core of Android’s SDK. Java has a couple of interesting properties that make it different to other programming languages like C.

Gary Explains:

  • Java vs C app performance – Gary explains
  • Java vs C app performance – Gary explains
  • Java vs C app performance – Gary explains
  • Java vs C app performance – Gary explains
  • Java vs C app performance – Gary explains
  • Java vs C app performance – Gary explains

First of all Java doesn’t (generally) compile to native machine code. Instead it compiles to an intermediate language known as Java bytecode, the instruction set of the Java Virtual Machine (JVM). When the app is run on Android it is executed via the JVM which in turn runs the code on the native CPU (ARM, MIPS, Intel).

ADVERTISEMENT

Secondly, Java uses automated memory management and as such implements a garbage collector (GC). The idea is that programmers don’t need to worry about which memory needs to be freed as the JVM will keep track of what is needed and once a section of memory is no longer being used the garbage collector will free it. The key benefit is a reduction in run time memory leaks.

The C programming language is the polar opposite to Java in these two respects. First, C code is compiled to native machine code and doesn’t require the use of a virtual machine for interpretation. Second, it uses manual memory management and doesn’t have a garbage collector. In C, the programmer is required to keep track of the objects that have been allocated and free them as and when necessary.

While there are philosophical design differences between Java and C, there are also performance differences.

There are other differences between the two languages, however they have less of an impact of the respective levels of performance. For example, Java is an object orientated language, C is not. C relies heavily on pointer arithmetic, Java does not. And so on…

Performance

So while there are philosophical design differences between Java and C, there are also performance differences. The use of a virtual machine adds an extra layer to Java that isn’t needed for C. Although using a virtual machine has its advantages including high portability (i.e. the same Java based Android app can run on ARM and Intel devices without modification), Java code runs slower than C code because it has to go through the extra interpretation stage. There are technologies which have reduced this overhead to the barest minimum (and we will look at those in a moment), however since Java apps aren’t compiled to the native machine code of a device’s CPU then they will always be slower.

The other big factor is the garbage collector. The problem is that garbage collection takes time, plus it can run at any time. This means that a Java program which creates lots of temporary objects (note that some types of String operations can be bad for this) will often trigger the garbage collector, which in turn will slow down the program (app).

Google recommends using the NDK for ‘CPU-intensive applications such as game engines, signal processing, and physics simulations.’

So the combination of interpretation via the JVM, plus the extra load because of garbage collection means that Java programs run slower in the C programs. Having said all that, these overheads are often seen as a necessary evil, a fact of life inherent in using Java, but the benefits of Java over C in terms of its “write once, run anywhere” designs, plus it object orientated-ness means that Java could still be considered the best choice.

That is arguably true on desktops and servers, but here we are dealing with mobile and on mobile every bit of extra processing costs battery life. Since the decision to use Java for Android was made in some meeting somewhere in Palo Alto back in 2003 then there is little point in bemoaning that decision.

While the primary language of the Android Software Development Kit (SDK) is Java, it isn’t the only way to write apps for Android. Alongside the SDK, Google also has the Native Development Kit (NDK) which enables app developers to use native-code languages such as C and C++. Google recommends using the NDK for “CPU-intensive applications such as game engines, signal processing, and physics simulations.”

SDK vs NDK

All this theory is very nice, but some actual data, some numbers to analyse would be good at this point. What is the speed difference between a Java app built using the SDK and a C app made using the NDK? To test this I wrote a special app which implements various functions in both Java and C. The time taken to execute the functions in Java and in C is measured in nanoseconds and reported by the app, for comparison.

Best Android Apps:

  • Java vs C app performance – Gary explains
  • Java vs C app performance – Gary explains
  • Java vs C app performance – Gary explains
  • Java vs C app performance – Gary explains

This all sounds relatively elementary, however there are a few wrinkles that make this comparison less straightforward than I had hoped. My bane here is optimization. As I developed the different sections of the app, I found that small tweaks in the code could drastically change the performance results. For example, one section of the app calculates the SHA1 hash of a chunk of data. After the hash is calculated the hash value is converted from its binary integer form into a human readable string. Performing a single hash calculation doesn’t take much time, so to get a good benchmark the hashing function is called a 50,000 times. While optimizing the app, I found that improving the speed of the conversion from the binary hash value to the string value changed the relative timings significantly. In other words any change, of even a fraction of a second, would be magnified 50,000 times.

Now any software engineer knows about this and this problem isn’t new nor is it insurmountable, however I wanted to make two key points. 1) I spent several hours on optimizing this code, to the best results from both the Java and C sections of the app, however I am not infallible and there could be more optimizations possible. 2) If you are an app developer then optimizing your code is an essential part of the app development process, don’t ignore it.

My benchmark app does three things: First it repeatedly calculates the SHA1 of a block of data, in Java and then in C. Then it calculates the first 1 million primes using trial by division, again for Java and C. Finally it repeatedly runs an arbitrary function which performs lots of different mathematical functions (multiply, divide, with integers, with floating point numbers etc), both in Java and C.

The last two tests give us a high level of certainty about the equality of the Java and C functions. Java uses a lot of the style and syntax from C and as such, for trivial functions, it is very easy to copy between the two languages. Below is code to test if a number is prime (using trial by division) for Java and then for C, you will notice that they look very similar:

public boolean isprime(long a) {         if(a == 2){                 return true;         }else if(a <= 1 || a % 2 == 0){                 return false;         }         long max = (long)Math.sqrt(a);         for(long n= 3; n <= max; n+= 2){                 if(a % n == 0){ return false; }         }         return true; }

And now for C:

int my_is_prime(long a) {         long n;         if(a == 2){                 return 1;         }else if(a <= 1 || a % 2 == 0){                 return 0;         }         long max = sqrt(a);         for( n= 3; n <= max; n+= 2){                 if(a % n == 0){ return 0; }         }         return 1; }

Comparing the execution speed of code like this will show us the “raw” speed of running simple functions in both languages. The SHA1 test case however is quite different. There are two different sets of functions that can be used to calculate the hash. One is to use the built-in Android functions and the other is to use your own functions. The advantage of the first is that the Android functions will be highly optimized, however that is also a problem as it seems that many versions of Android implement these hashing functions in C, and even when Android API functions are called the app ends up running C code and not Java code.

So the only solution is to supply a SHA1 function for Java and a SHA1 function for C and run those. However, optimization is again a problem. Calculating a SHA1 hash is complex and these functions can be optimized. However optimizing a complex function is harder than optimizing a simple one. In the end I found two functions (one in Java and one in C) which are based on the algorithm (and code) published in RFC 3174 –  US Secure Hash Algorithm 1 (SHA1) . I ran them “as is” without trying to improve the implementation.

Different JVMs and different word lengths

Since the Java Virtual Machine is a key part in running Java programs it is important to note that different implementations of the JVM have different performance characteristics. On desktops and server the JVM is HotSpot, which is released by Oracle. However Android has its own JVM. Android 4.4 KitKat and prior versions of Android used Dalvik, written by Dan Bornstein, who named it after the fishing village of Dalvík in Eyjafjörður, Iceland. It served Android well for many years, however from Android 5.0 onwards the default JVM became ART (the Android Runtime). Whereas Davlik dynamically compiled frequently executed short segments bytecode into native machine code (a process known as just-in-time compilation), ART uses of ahead-of-time (AOT) compilation which compiles the whole app into native machine code when it is installed. The use of AOT should improve the overall execution efficiency and reduce power consumption.

ARM contributed large amounts of code to the Android Open Source Project to improve the efficiency of the bytecode compiler in ART.

Although Android has now switched over to ART, that doesn’t mean that is the end of JVM development for Android. Because ART converts the bytecode into machine code that means there is a compiler involved and compilers can be optimized to produced more efficient code.

For example, during 2015 ARM contributed large amounts of code to the Android Open Source Project to improve the efficiency of the bytecode compiler in ART. Known as the O ptimizing compiler it was a significant leap forward in terms of compiler technologies, plus it laid the foundations for further improvements in future releases of Android. ARM has implemented the AArch64 backend in partnership with Google.

What this all means is that the efficiency of the JVM on Android 4.4 KitKat will be different to that of Android 5.0 Lollipop, which in turn is different to that of Android 6.0 Marshmallow.

Besides the different JVMs there is also the issue of 32-bit versus 64-bit. If you look at the trial by division code above you will see that the code uses long integers. Traditionally integers are 32-bit in C and Java, while  long integers are 64-bit. A 32-bit system using 64-bit integers needs to do more work to perform 64-bit arithmetic when it only has 32-bits internally. It turns out that performing a modulus (remainder) operation in Java on 64-bit numbers is slow on 32-bit devices. However it seems that C doesn’t suffer from that problem.

The results

I ran my hybrid Java/C app on 21 different Android devices, with lots of help from my colleagues here at Android Authority. The Android versions include Android 4.4 KitKat, Android 5.0 Lollipop (including 5.1), Android 6.0 Marshmallow, and Android 7.0 N. Some of the devices were 32-bit ARMv7 and some were 64-bit ARMv8 devices.

The app doesn’t perform any multi-threading and doesn’t update the screen while performing the tests. This means that the number of cores on the device won’t influence the outcome. What is of interest to us is the relative difference between forming a task in Java and performing it in C. So while the tests results do show that the LG G5 is faster than the LG G4 (as you would expect), that isn’t the aim of these tests.

Overall the test results were clumped together according to Android version and system architecture (i.e. 32-bit or 64-bit). While there were some variations, the grouping was clear. To plot the graphs I used the best result from each category.

The first test is the SHA1 test. As expected Java runs slower than C. According to my analyse the garbage collector plays a significant role in slowing down the Java sections of the app. Here is a graph of the percentage difference between running Java and C.

Java vs C app performance – Gary explains

Starting with the worst score, 32-bit Android 5.0, shows that the Java code ran 296% slower than C, or in other words 4 times slower. Again, remember that the absolute speed isn’t important here, but rather the difference in the time taken to run the Java code compared to the C code, on the same device. 32-bit Android 4.4 KitKat with its Dalvik JVM is a little bit faster at 237%. Once the jump is made to Android 6.0 Marshmallow things start to improve dramatically, with 64-bit Android 6.0 yielding the smallest difference between Java and C.

The second test is the prime number test, using trial by division. As noted above this code uses 64-bit long integers and will therefore favor 64-bit processors.

Java vs C app performance – Gary explains

As expected the best results come from Android running on 64-bit processors. For 64-bit Android 6.0 the speed difference is very small, just 3%. While for 64-bit Android 5.0 it is 38%. This demonstrates the improvements between ART on Android 5.0 and the Optimizing compiler used by ART in Android 6.0. Since Android 7.0 N is still a development beta I haven’t shown the results, however it is generally performing as well as Android 6.0 M, if not better. The worse results are for the 32-bit versions of Android and oddly 32-bit Android 6.0 yields the worst results of the group.

The third and final test executes a heavy mathematical function for a million iterations. The function does integer arithmetic as well as floating point arithmetic.

Java vs C app performance – Gary explains

And here for the first time we have a result where Java actually runs faster than C! There are two possible explanations for this and both are to do with optimization and the O ptimizing  compiler from ARM. First, the O ptimizing compiler could have produced more optimal code for AArch64, with better register allocation etc., than the C compiler in Android Studio. A better compiler always means better performance. Also there could be a path through the code which the O ptimizing  compiler has calculated can be optimized away because it has no influence on the final result, but the C compiler has not spotted this optimization. I know that this kind of optimization was one of the big focuses for the O ptimizing  compiler in Android 6.0. Since the function is just a pure invention on my part, there could be a way to optimize the code that omits some sections, but I haven’t spotted it. The other reason is that calling this function, even one million times, doesn’t cause the garbage collector to run.

As with the primes test, this test uses 64-bit long integers, which is why the next best score comes from 64-bit Android 5.0. Then comes 32-bit Android 6.0, followed by 32-bit Android 5.0, and finally 32-bit Android 4.4.

Wrap-up

Overall C is faster than Java, however the gap between the two has been drastically reduced with the release of 64-bit Android 6.0 Marshmallow. Of course in the real world, the decision to use Java or C isn’t black and white. While C has some advantages, all of the Android UI, all of the Android services, and all of the Android APIs are designed to be called from Java. C can really only be used when you want a blank OpenGL canvas and you want to draw on that canvas without using any Android APIs.

However if your app has some heavy lifting to do, then those parts could be ported to C and you might see a speed improvement, however not as much as you once could have seen.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Java vs C app performance – Gary explains

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址