Java VS Python
A property and performance comparison between the two languages
By August Eilertsen & Kasper Berg
This report will provide a detailed description of differences in Java and Python such as performance, runtime, and memory usage. We will start by separately look at each language and run different algorithms while using profiling tools to monitor the program with a focus on the previously mentioned properties. The same algorithms will be executed in both of the languages. After individually executing performance and memory tests, we will compare and explain the results
Software:
Environment
It is worth to keep in mind that every test executed will differ in results in every execution due to how the memory on the machine executed on is organized at the moment of execution, and how much CPU that is available at the moment. Therefore the results provided will be an average of all the executions.
1. A simple start
See appendix 4.1 for the laptop environment.
To start off with the basics, the idea is to run two identical programs written in Java and Python. The program consists of loops making 5 000 000 and 10 000 000 objects. The program will re-iterate over the objects several times.
1.1 Time and memory profiling with YourKit
To be able to run a profiling test, a delay of 15 seconds is included in the program to be able to make ready for YouKit to profile the program.
Memory usage & execution time
Test 1 (5 000 000 objects) | Test 2 (10 000 000 objects) | |
Time | 2 seconds | 4 seconds |
Memory (See appendix 1.1 & 1.2 for graphs) | Heap: 484 MB allocated, 290MB being used at most. Non-heap: 31 MB allocated, 26 MB is being used at most. | Heap: 966 MB allocated, 629 MB being used at most. Non-heap: 31 MB allocated, 27 MB used |
The G1 eden space is the “young generation” space. This is where newly arriving memory/objects is initially allocated to. After the garbage collector kicks in, objects are copied to the G1 Old Gen space, which is the “old generation”. This means older object which have not been referenced to as recent as the object in the eden space. The G1 survivor space is the pool containing objects that have survived the garbage collection of the Eden space. At each peak and drop in the graph, we can see that the garbage collector kicks in. This performs a mark & sweep algorithm to clean up space. The G1 garbage collector is a “stop-the-world” GC, which means that at each collection, execution is paused for some small amount of time.
1.2 Time and memory profiling with memory-profiler
As with the java profiler, the profiler introduces a lot of overhead when profiling memory.
Time & memory usage (see appendix 1.3 for graphs and 1.4 for outputs)
Test 1 (5 000 000 iterations) | Test2 (10 000 000 iterations) | |
Time | 1 min and 23 seconds | 3 min and 1 second |
Memory | 1200MB used at most | 2350 MB used at most |
1.3 Comparison
The difference between the two languages in this simple example are quite big. The python program uses substantially more time and memory than the java program. This has much to do with the garbage collection functionality in Java. The Garbage Collector is invoked 20 times in test 1 and 24 times in test 2, freeing memory. The python interpreter uses reference counting all the time while the program is being executed. This is not able to clean up nearly as much memory as the Java GC.
See appendix 4.1 for the laptop environment.
Next, we want to run something which will show more significant differences; The Mandelbrot set.
2.1 Time and memory profiling with YourKit
A delay of 20 seconds is also added here.
Execution time and memory usage
Time (N = 15 000) | 10 seconds |
Memory (see appendix 2.1 for graphs) | Heap: 128 MB allocated, 33 MB was used at most. Non-heap: 31 MB was allocated, 27 MB was used. |
2.2 Time and memory profiling with cProfile and memory profiler
Execution time and memory usage
Time (N = 15 000) | 11 minutes |
Memory | Memory usage at most: 11.8 MB. |
Output | See appendix 2.2 |
2.3 Comparison
The differences, in this case, are large. The java program executes just in a small fraction of time compared to the python program. 10 seconds vs 11 minutes. To the contrary, there is not much difference in memory. Looking at the heap-memory, Java uses 33 MB vs Python with 11.8.
See appendix 4.2 for the laptop environment.
Next, The N-body problem. This is a problem of predicting the individual motions of a group of celestial objects interacting with each other gravitationally.
3.1 Time and memory profiling with YourKit
Execution time and memory usage
Time (N = 50 000 000) | 5 seconds | |
Memory | Heap: 245 MB allocated, 11 MB was used at most. Non-heap: 10 MB was allocated, 9.8 MB was used. | See appendix 3.1 for graphs |
3.2 Time and memory profiling with cProfile
Execution time and memory usage
For python, running n-body, was significantly more time-consuming.
Time (N=50000000) | 675 seconds ~11 min, 15 sec | |
Memory | A total of 15MB | See appendix 3.2 for graph and output |
3.3 Comparison
Similar to Mandelbrot, the differences from N-body are quite significant. A runtime of 5 vs 640 seconds and memory usage of ~21MB vs ~15MB.
4. Findings
Python is dynamically typed and interpreted language. The performance drawback that comes with these properties are the reasons for the big time difference. In the Mandelbrot and N-body programs, there are a lot of mathematical computations and variable assignments. A lot of different variables are assigned different values and performed arithmetic operations on at runtime, and the interpreter has to use a lot of time to determine the different variable types of the outcomes. The code is also not compiled, which makes it slower when executed.
In Mandelbrot and N-body, the memory difference can be explained by both the fact that java compiles the code and by looking at the code.
In Mandelbrot, at program start, a 15 000 x 15 000 matrix is being initialized, and further modified during execution. Likewise in N-body, the celestial objects are being initialized at program start, which is what takes up memory. During the 11 minutes (Mandelbrot) and 10 min, 40 sec (N-body) python uses to complete the executions, there is the only reallocation of memory, not allocating more as the time goes, as we saw with the simple program, where we have an ever-increasing array. The compiled code uses a lot of memory in this case compared to the memory usage of the python program. To the contrary, in the simple program the compiled code will only be a small part of the memory usage, and because of the large number of iterations with a very large array, this will dominate the memory usage.
5. Conclusion
The Java language is statically typed, which means that every variable must be explicitly declared. Java’s efficiency largely comes from its JIT-compiler and the support for concurrency. JIT is a part of the Java Runtime Environment which improves the performance of Java programs by compiling bytecodes into native machine code “just in time” to run. The optimizer will see the different parts of the application that are being executed the most and optimize specific parts. Java Virtual Machine calls the compiled code directly. Since the code is not interpreted, we don’t require processor time and memory usage when compiling.
Python, on the other hand, is interpreted which slows down Python programs during runtime. Determining the variable type during runtime increases the workload of the interpreter. Also, remembering the object types contributes to memory usage.
In our tests, the winner with regards to both time and memory was Java. Since there is a lot of looping, a lot of mathematical and arithmetical computations and many objects to handle, the python interpreter and the dynamical nature of python introduces a lot of overhead.
Appendix
1.1 |
| ||||
1.2 | 10 000 000 objects | ||||
1.3 |
| ||||
1.4 |
| ||||
1.4 | |||||
1.5 | |||||
1.6 | |||||
2.1 | |||||
2.2 | |||||
3.1 | |||||
3.2 | |||||
4.1 | MacBook Pro with the following specifications:
| ||||
4.2 | MacBook Pro with the following specifications:
|