September 1, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Java vs. C++: The Performance Showdown

  • January 6, 2010
  • By Liviu Tudor
  • Send Email »
  • More Articles »

Memory Allocation

One of the arguments C++ programmers are confronted with quite often is the memory management issue. While Java looks after this for you, C++ memory management is subject to constructors and destructors and the dreaded new/delete pair!

To compare memory allocation performance, start with a simple task: allocating an array of 1,000 bytes repeatedly and measuring the time it takes to allocate and de-allocate it. This will be a tricky task in Java because there is no definite way to free up memory (as a reminder, System.gc is merely a suggestion to the JVM that it may free up some memory if needed, but there is no guarantee). However, just accept that the measurement taken in Java might not include the freeing up of memory timing, as you will still use them in the comparison. (After all, as stated before, one of the main arguments between C++ and Java developers is that you don’t have to worry about memory management in Java whereas you do have to take time to handle it in C++!)

Notice that you are talking array of bytes—not integers! That’s because in Java the integer has a different size from the integer in C++, and allocating different types of arrays would be unfair!

In Java, the function for allocation therefore looks like this (IntAlloc.java):

   private void javaAlloc()
   {
      System.gc();
      elements = new byte[N_ELEMS];
      elements[0] = 0;
   }

Note that you are first suggesting the garbage collection and then allocating the memory. In order to prevent the compiler from being “clever” and not allocating the memory until it’s actually accessed, you’re forcing it to by setting the first element in the array.

Also, for the purpose of this test, you're going to measure your time in nanoseconds because milliseconds might not be enough. In Java, you'll be using the System.nanoTime function for this exercise. (May I remind you that a millisecond = 1,000,000 nanoseconds!)

Bearing this in mind, the average timing of running the above looks like this:

Java memory allocation took 11,755,693

So it took about 11 milliseconds for 10,000 bytes to be allocated. Let’s try the above without suggesting the GC (simply commenting out the System.gc line in the javaAlloc function above):

Java memory allocation took 12,994

This indicates that occasionally, as a result of your suggesting the GC via System.gc(), the garbage collection kicks in and recollects the memory used. It also suggests that if you don’t worry about the garbage collection, it takes less than a millisecond to allocate 10,000 bytes—more specifically, it takes about 13,000 nanoseconds—a rough average of 1.3 nanoseconds per byte!

Now let’s try this in C++ (IntAlloc project included in the JavaVsCPP.zip code download):

C allocation took 3661.273463

This might come as a surprise to you, but guess what: The memory allocation in C++ is comparable to Java's. It’s hard to tell based on these measurements which one is faster, as they are all less than a millisecond. However, they are of similar magnitude. And again, bear in mind that this is the result of the C++ optimized code you're comparing against!

As for not using the System.gc, ask any Java programmer and you will find that apart from those programming for real-time systems, very few in fact actually use the System.gc. They mostly leave the memory management to the JVM—as it is implemented cleverly enough to “know” when to kick in so it doesn’t affect system performance that much.

It is interesting to note though, for those of you who do have the patience to go through my code and change it a bit, that if you change the allocation to int—neither the Java nor the C++ “suffers” that much!

You’ve seen how both languages perform when it comes to primitive data types. What if you take this one step further: how about objects? For this, you'll consider a class that maps the “complex number” data type, a type with two components: a “real” part and an “imaginary” part—both being floating point numbers. So let’s try to implement this class in both languages and then try to instantiate it a few times and see whether you can draw any conclusions from that.

In Java, as you would imagine, you simply have two private members that store the data and expose it via getters and setters. Also, as to be expected, on top of the default constructor you'll provide a constructor that takes two values and initializes the members with the values provided (Complex.java):

public class Complex 
{
   private double real;
   private double imaginary;
   
   public Complex()
   {
      this( 0.0, 0.0 );
   }
   
   public Complex( double real, double imaginary )
   {
      this.real = real;
      this.imaginary = imaginary;
   }
 
   public double getReal() 
   {
      return real;
   }
   public void setReal(double real) 
   {
      this.real = real;
   }
   
   
   public double getImaginary() 
   {
      return imaginary;
   }
   public void setImaginary(double imaginary) 
   {
      this.imaginary = imaginary;
   }
}

Similarly, C++ supports the following implementation:

class Complex
{
    public:
        Complex(double real = 0, double imaginary = 0) 
        {
            this->real = real;
            this->imaginary = imaginary;
        }
        
        double getReal()
        {
            return real;
        }
        void setReal( double real ) 
        {
            this->real = real;
        }
        
        double getImaginary()
        {
            return imaginary;
        }
        void setImaginary( double imaginary )
        {
            this->imaginary = imaginary;
        }
        
    private:
        double real;
        double imaginary;
};

The code used (in the ComplexCreate project included in the JavaVsCPP.zip code download and in ComplexCreate.java) is very simple. However, a major difference is that the instantiation process in Java takes two steps:

  1. Allocate memory for the actual array
  2. Create each object

In C++, however, you can do this in one step:

arr = new Complex[N_GENERATED];

Using the new operator will in fact create and initialize the items in one step. Looking at the execution of the C++ and Java code side by side, you get the following results:

(Java) Java create took 710788
 
(C++) C instantiation took 29348.262052

These times are again in nanoseconds. And for those who can’t figure it out, the difference is about 710,000 (Java) compared with 29,000 (C++). Ahem, no comment :D

C++ Wins—with Some Help

There certainly is a lot more to a programming language than memory allocation, looping, and floating-point operation, but you will struggle to find a program that doesn’t use at least one of those. And when it does—whether you like it or not—an optimized C++ compiler can deliver faster code than Java. However, that does raise the question of whether Java compilation (I’ve used the standard JDK compiler with no extra flags) itself needs more optimizations that will improve execution times in cases similar to the ones shown above.

Code Download

  • JavaVsCPP.zip
  • About the Author

    Liviu Tudor is a Java consultant living in the UK with a lot of experience with high-availability systems, mostly in the online media sector. Throughout his years of working with Java, he came to realize that when performance matters, it is the "low level," core Java that makes an application deliver rather than a bloated middleware framework. When healing from the injuries acquired playing rugby with his team, The Phantoms (http://www.phantomsrfc.com) in London, he writes articles on Java technology for Developer.com.



    Page 3 of 3



    Comment and Contribute

     


    (Maximum characters: 1200). You have characters left.

     

     


    Sitemap | Contact Us

    Rocket Fuel