7

The following code proves that method1 is faster than method2. Can anyone please comment what is the reason for such behavior.

class Trial {
        String _member;
        void method1() {
            for(int i=0;i<30480;i++) {
                _member += "test";
            }
        }
        void method2() {
            String temp="";
            for(int i=0;i<30480;i++) {
                temp += "test";
            }
            _member = temp;
        }

        public static void main(String args[]) {
            Trial t = new Trial();
            long startTime1 = System.currentTimeMillis();
            t.method1();
            long endTime1 = System.currentTimeMillis();
            long startTime2 = System.currentTimeMillis();
            t.method2();
            long endTime2 = System.currentTimeMillis();
            System.out.println(endTime1 - startTime1);
            System.out.println(endTime2 - startTime2);
        }
    }
Samuel Liew
  • 76,741
  • 107
  • 159
  • 260
Amber
  • 1,229
  • 9
  • 29
  • 1
    How many times did you run your test? Did you try and run the two variants many times in a loop? Did you try and invert the order of your calls? – Nicola Musatti Jun 18 '13 at 07:14
  • Try to implement benchmark using [jmh](http://openjdk.java.net/projects/code-tools/jmh/). It is special tool designed by jvm developers. You can avoid many mistakes using this tool instead of your own benchmark. – Mikita Belahlazau Jun 18 '13 at 07:14
  • i tried it 10 times, and the second always runs faster,. – simaremare Jun 18 '13 at 07:15
  • Additionally to what Nicola said, method2 puts a LOT of used memory (the previous contents of _member) up for garbage collection. That might ruin your timing. – Nicktar Jun 18 '13 at 07:15
  • You seems to be doing additional work in method2 _member = temp; – Juned Ahsan Jun 18 '13 at 07:16
  • @NicolaMusatti I ran the test almost 20 times with consistent result. I could assume that it may be hardware dependent but again there may be some logical answer to it. – Amber Jun 18 '13 at 07:20
  • @JunedAhsan Even without the last additional step method1 is faster for me. – Amber Jun 18 '13 at 07:21
  • i have a test a few months ago, and found that the getCurrentTimeMillis() cannot show the real CPU execution time,. – simaremare Jun 18 '13 at 07:22
  • No answer to the question yet. **Local member is faster or instance member?** Do we really need to benchmark to know what is fast? No one here knows the internals of Java? I'm eagerly waiting for an answer which explains the exact working of Java and what to prefer for performance improvement. – Niranjan Jun 18 '13 at 07:53
  • @Niranjan `Do we really need to benchmark to know what is fast?` - Yes. It is not only the Java compiler or JVM which influences the results, but also other parts of the technology stack like the JIT compiler and the underlying hardware. When you analyze the bytecode for both variants, you will find some additional instructions in the `for()`-loop of `method1()` - but whether they have a significant impact on performance depends on additional factors, e.g. how does the JIT compiler translate the bytecode to native instructions. – Andreas Fester Jun 18 '13 at 08:50

3 Answers3

12

The following code proves that method1 is faster than method2

No. It does not prove it.

It depends on many factors. When I run this code, I get

1403
1248

So in my environment, your code "proves" that method1 is slower than method2.

When doing benchmarking, you need to take care of effects like caching and JVM warmup.

See also

for more information.


I slightly refactored the main method:

...

static void doBenchmark() {
   Trial t = new Trial();

   long startTime1 = System.currentTimeMillis();
   t.method1();
   long endTime1 = System.currentTimeMillis();

   long startTime2 = System.currentTimeMillis();
   t.method2();
   long endTime2 = System.currentTimeMillis();

   System.out.println(endTime1 - startTime1);
   System.out.println(endTime2 - startTime2);
}

public static void main(String args[]) {

   for (int i = 0;  i < 20;  i++) {
      doBenchmark();
      System.out.println("----");
   }
}

This results in similar values for the first iteration of the for-loop, but then the results converge and do not differ significantly anymore:

1396
1133
----
1052
1070
----
688
711
----
728
726
----
715
709
----
...

Even, sometimes method1 seems faster and sometimes method2 - this is most likely due to measurement inaccuracy.

Community
  • 1
  • 1
Andreas Fester
  • 36,091
  • 7
  • 95
  • 123
  • Again the same question, why method2 is faster than method1...Lol....+1 for benchmarking..... – Akash KC Jun 18 '13 at 07:24
  • 1
    I suppose i require to verify my findings again using some benchmark tool. I will post the response as soon as i am done with that. – Amber Jun 18 '13 at 07:25
0

After warming up the jvm for some times, you will see that, method2 is faster than method1.

Here is my re-factored code:

class Trial {
String _member;

  void method1() {
    for (int i = 0; i < 30480; i++) {
        _member += "test";
    }
  }

  void method2() {
    String temp = "";
    for (int i = 0; i < 30480; i++) {
        temp += "test";
    }
    _member = temp;
  }

  public static void main(String args[]) {
    Trial t = new Trial();

    for (int i = 0; i < 10; i++) { //warm up jvm
        t.method1();
        t.method2();
    }

    t = new Trial();

    long startTime1 = System.currentTimeMillis();
    t.method1();
    long endTime1 = System.currentTimeMillis();
    long startTime2 = System.currentTimeMillis();
    t.method2();
    long endTime2 = System.currentTimeMillis();
    System.out.println(endTime1 - startTime1);
    System.out.println(endTime2 - startTime2);
  }
}

And here is the result:

----
2910
2894
----

But for actual benchmarking, you should run it for several times and observe the statistical behaviors and only then you can draw any conclusion!

Sazzadur Rahaman
  • 6,938
  • 1
  • 30
  • 52
  • Well - you made the same mistake as I did during my refactoring :D In order to really compare the methods, you need to re-instantiate `Trial` inside the `for`-loop. Otherwise you work with the old contents in the `String _member` variable. Since there is already content in there, the additional concatenations will take significantly more time. – Andreas Fester Jun 18 '13 at 07:45
0

I've modified the number of test, but not the method, here with StringBuilder 2500 at 3000 time more fast!

class Trial {
    StringBuilder _member = new StringBuilder(243840);
    void method1() {
        for (int i = 0; i < 30480; i++) {
            _member.append("test");
        }
    }

    void method2() {
        final StringBuilder temp = new StringBuilder(243840);
        for (int i = 0; i < 30480; i++) {
            temp.append("test");
        }
        _member = temp;
    }
    public static void main(final String args[]) {
        long startTime1 = System.nanoTime();
        new Trial().method1();
        long endTime1 = System.nanoTime();
        long startTime2 = System.nanoTime();
        new Trial().method2();
        long endTime2 = System.nanoTime();
        System.out.println(endTime1 - startTime1);
        System.out.println(endTime2 - startTime2);
        System.out.println("------------------");
        startTime1 = System.nanoTime();
        new Trial().method1();
        endTime1 = System.nanoTime();
        startTime2 = System.nanoTime();
        new Trial().method2();
        endTime2 = System.nanoTime();
        System.out.println(endTime1 - startTime1);
        System.out.println(endTime2 - startTime2);
    }
}

The output:

method1 then method2 with += in MILLIisecond
5563
5844
............................................
5437
6344

method2 then method1 with += in MILLIisecond
4625
5969
------------------
6531
4891

=====================================================

method1 then method2 with StringBuilder in NANOsecond
3530337
2074286
------------------
2058641
1983493
.....................................................

method2 then method1 with StringBuilder in NANOsecond
3430883
1698819
------------------
2065626
2144406

So As @Andreas said it is not a good way to test performances.

Thing to point out : use StringBuilder with declared size (In Joshua Bloch's book Effective Java Item 51: Beware the performance of string concatenation)
- prefer method2() when it possible : the String[Builder] is déclared inside it, and not use outside

cl-r
  • 1,264
  • 1
  • 12
  • 26