2

I had the idea I would turn some of my if blocks into single lines, using the conditional operator. However, I was wondering if there would be a speed discrepancy. I ran the following test:

static long startTime;
static long elapsedTime;
static String s;
    
public static void main(String[] args) {
    startTime = System.nanoTime();
    s = "";
    for (int i= 0; i < 1000000000; i++) {
        if (s.equals("")) {
            s = "";
        }
    }
    
    elapsedTime = System.nanoTime() - startTime;
    
    System.out.println("Type 1 took this long: " + elapsedTime + " ns");
    
    startTime = System.nanoTime();
    s = "";
    for (int i= 0; i < 1000000000; i++) {
        s = (s.equals("") ? "" : s);
    }
    
    elapsedTime = System.nanoTime() - startTime;
    
    System.out.println("Type 2 took this long: " + elapsedTime + " ns");
}

This is my result:

Type 1 took this long: 3293937157 ns

Type 2 took this long: 2856769127 ns

Am I doing something wrong here?

Assuming s.equals("") necessarily is true, is this a viable way to make your code faster?

Community
  • 1
  • 1
Adam Jensen
  • 541
  • 1
  • 10
  • 25
  • I ran your code, I got the same results for both types. – icza Jul 30 '14 at 06:08
  • 3
    The first one seems *slightly* slower because you are *creating* and *adding* the String `""` into the String pool. In the second case, it is already available in the string pool. So, you save that time. Try putting `startTime = System.nanoTime();` after `s = "";` in the beginning and check. Some things are better understood by the *compiler / JVM* and are machine dependent. Don't rely on such benchmarks. – TheLostMind Jul 30 '14 at 06:09
  • Good call. I ran it again with similar results though. 1: 3276392684 ns and 2: 2851092293 ns – Adam Jensen Jul 30 '14 at 06:12
  • I ran your code and got: `Type 1 took this long: 649458000 ns Type 2 took this long: 654508000 ns` but in another run I got: `Type 1 took this long: 649534000 ns Type 2 took this long: 646107000 ns` -what's the meaning ? ;) – Nir Alfasi Jul 30 '14 at 06:13
  • Type 2 is equivalent to if (s.equals("")) s = ""; else s = s; How does Type 2 be more quicker? :) – Maas Jul 30 '14 at 06:14
  • 4
    Another "small" thing: when implementing a benchmark - you should provide a "warmup". – Nir Alfasi Jul 30 '14 at 06:15
  • 2
    try changing the order and do test2 first. Result: The test you run last is quickest. – Scary Wombat Jul 30 '14 at 06:15
  • 4
    @alfasin's second suggestion isn't just a small thing (which I suspect he knows :) ). Your first loop is starting out on interpreted code (e.g. for `String.equals`) and then compiling and optimizing it as it goes. The second loop gets all of that benefit without having to do any of the work. Microbenchmarks like this are notoriously difficult in Java; I suggest you read up on the many pages out there that have been written on it (google "java microbenchmark"). – yshavit Jul 30 '14 at 06:19
  • 1
    As far as SO resources, check out http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java (though I don't quite feel comfortable closing this as a duplicate of that one). – yshavit Jul 30 '14 at 06:20
  • @yshavit - spot on... :).. Alfasin's comment could actually be *the* answer. – TheLostMind Jul 30 '14 at 06:20
  • I use javap and only difference lies in `27: ifeq 35 30: ldc #4 // String 32: putstatic #5 // Field s:Ljava/lang/String;` and `110: ifeq 118 113: ldc #4 // String 115: goto 121 118: getstatic #5 // Field s:Ljava/lang/String; 121: putstatic #5 // Field s:Ljava/lang/String;` – Tony Jul 30 '14 at 06:35
  • 1
    @Tony - It is extremely hard to read what you've posted as a *comment*. – TheLostMind Jul 30 '14 at 06:38
  • 2
    Yes, you are doing several things wrong. The first is premature optimization, the second thing is thinking that if is your bottleneck. Since you've tagged your question with profiling, what did the profiler tell you was the slowest bit of code in your real world application? – Mikkel Løkke Jul 30 '14 at 07:14
  • @MikkelLøkke I tried this because I wanted 3 liners to be 1 liners in my code, not because I thought there was a bottleneck. Also, this _is_ the profiler. The "what am I doing wrong" is regarding the code. I didn't even know the JVM was doing optimization. – Adam Jensen Jul 30 '14 at 07:21
  • 1
    This is a benchmark, not a profiler. A benchmark asks "how long does this task takes?" while a profiler looks at the running system and says "what parts of the code are taking the most time?" – yshavit Jul 30 '14 at 15:18
  • Cheers. I corrected the tags accordingly. – Adam Jensen Jul 31 '14 at 00:04
  • @alfasin would you perhaps like to "answer" the question? (or yshavit, or anyone who would like to mention that the speed discrepancy is a result of the order / JVM optimization) – Adam Jensen Jul 31 '14 at 00:18
  • @AdamJensen sure, see below. – Nir Alfasi Jul 31 '14 at 04:44

3 Answers3

7
, is this a viable way to make your code faster?

You can even make it faster if your String s; is a non static field. Static-field is slower than non-static field when you are referencing it a billion times

public static void main(String[] args) {

    startTime = System.nanoTime();
    String s = "";
    .
    .
}

EDIT:

Why is it faster??

It is due to the referencing of string to the static field.

You can see it in the byte code of it

    0: ldc           #23                 // String
       2: putstatic     #25                 // Field s:Ljava/lang/String;
       5: iconst_0
       6: istore_1
       7: goto          22
      10: getstatic     #25                 // Field s:Ljava/lang/String;
      13: ldc           #23                 // String
      15: invokevirtual #27                 // Method java/lang/String.equals:(L
java/lang/Object;)Z
      18: pop
      19: iinc          1, 1
      22: iload_1
      23: ldc           #33                 // int 1000000000
      25: if_icmplt     10
      28: return

As you can see getStatic and putStatic will be called a billion times, what it does is that it will call the reference of the static field and put the reference of the string using putStatic

getStatic - get a static field value of a class, where the field is identified by field reference in the constant pool index (index1 << 8 + index2)

putStatic - set static field to value in a class, where the field is identified by a field reference index in constant pool (indexbyte1 << 8 + indexbyte2)

See those bit shifting that is the cause of the slowness of the program

Also if you are using a global/member field it will create the same bytecode but instead it will use getfield and putfield which is the same as static's getStatic and putStatic

Now let see the non static field bytecode

      0: ldc           #21                 // String
       2: astore_1
       3: iconst_0
       4: istore_2
       5: goto          23
       8: aload_1
       9: ldc           #21                 // String
      11: invokevirtual #23                 // Method java/lang/String.equals:(L
java/lang/Object;)Z
      14: ifeq          20
      17: ldc           #21                 // String
      19: astore_1
      20: iinc          2, 1
      23: iload_2
      24: ldc           #29                 // int 1000000000
      26: if_icmplt     8
      29: return

As you can see it only uses astore_1 and aload_1 to save and load the reference of the non static field without extra operation.

Rod_Algonquin
  • 26,074
  • 6
  • 52
  • 63
  • 5
    Can you support your claim that "Static-field is slower than non-static field " by providing references/links ? – Nir Alfasi Jul 30 '14 at 06:18
  • I like your reasoning but I'm still not convinced that 8 times shift-left is slower than `aload_1` or `astore_1`. I remember reading that [Doug lea](http://en.wikipedia.org/wiki/Doug_Lea) is declaring local method variables as `final` whenever he can because it improves performance. But I never ran into a claim such as yours! again, can you back it up with *real* benchmarks, literature or any other valid reference ? – Nir Alfasi Jul 30 '14 at 07:23
  • what aload and astore is doing is load and store to the stack nothing complicated a simple instruction, and getStatic and putStatic is calculating , loading and storing. "declaring local method variables as final" that is because final cant be reference again and thus it is faster. I dont have reference to any link but looking at bytecode is enough. – Rod_Algonquin Jul 30 '14 at 07:50
  • It's not that "final can't be reference again" - it can't be modified, which theoretically enables compiler optimizations, but that's probably what you meant. BTW, when you write "non-static" do you mean local variable (method scope) or a class member variable which is not static ? – Nir Alfasi Jul 30 '14 at 07:54
  • @alfasin non static local variable – Rod_Algonquin Jul 30 '14 at 07:56
  • @alfasin if it is a member variable it will use the getfield and putfield which is the same as the static field's getstatic and putstatic – Rod_Algonquin Jul 30 '14 at 08:02
  • That's what I suspected... (you might want to clarify that in your answer!) +1 for the useful information even though I'm still not convinced before I see some benchmarking :) – Nir Alfasi Jul 30 '14 at 08:02
  • @alfasin will do. :)) – Rod_Algonquin Jul 30 '14 at 08:07
  • So, to clarify: are you saying that the way I did it _is_ a viable way to make the code faster (and in addition to that, I can make it even faster by using non-static fields)? – Adam Jensen Jul 31 '14 at 00:39
  • @AdamJensen yes and using non static local field will greatly improve performance, try it. – Rod_Algonquin Jul 31 '14 at 05:05
  • @Rod_Algonquin Other answers/comments suggest that it is _not_ viable, and the main reason my code was faster was because of the order of the two tests I ran. What you say about non static local fields seems to have merit, however. – Adam Jensen Aug 01 '14 at 06:38
5

This does smell like premature optimization to me. If you still intend to microbenchmark both implementations this way, I suggest using isEmpty() instead since the underlying code for that is more straightforward compared to equals(). By that, I mean any optimization that the compiler/JVM will do for you will be less likely triggered by what's happening in equals(), and more reflective of any minute benefits that one implementation has over the other, assuming that really matters.

Readability should be the better rule for you to decide whether you want to use if-else or ? :.

h.j.k.
  • 1,357
  • 2
  • 20
  • 30
2

The other answers have useful information that is relevant but none of them addresses the real question if the first form is more efficient than the second form.

This benchmarking does not provide reliable results since it's not done properly: one important "rule of thumb" in benchmarking Java code is to provide a warm-up. In this case, the first loop provides a warm-up to the second loop.

This answer provides additional instructions for micro-benchmarking as well as some useful links.

Community
  • 1
  • 1
Nir Alfasi
  • 53,191
  • 11
  • 86
  • 129