3

Could someone explain to me what is going on here...

Scenario 1

This:

// around 50ms
for (int i = 0; i < 3000000; i++) {
    String str = new String();
}

... is more time consuming than this:

// around 25ms
for (int i = 0; i < 3000000; i++) {
    String str = new String("");
}

Scenario 2

This:

String str = new String(); // around 3000ns

is less time consuming than this:

String str = new String(""); // around 5000ns

Why is calling the empty String() constructor more time consuming in scenario 1, but not in scenario 2? I had a look at the doc for String() and String(String original), but I could see no optimization there. Is this optimization (if it indeed is optimization) done somewhere else?

Updates:

How I'm timing this:

long start = System.nanoTime();
//doing stuff here
long elapsedTime = System.nanoTime() - start;

My system:

Windows 7 x64, using Java 7 and Eclipse

Roger
  • 2,684
  • 4
  • 36
  • 51
  • What Java version are you testing on? How did you time this, and what times did you get? – user2357112 Jul 30 '14 at 23:14
  • With `String str = new String("")`, you're creating TWO strings, first for the static empty string and second for the `new`. `String str = ""` is the same as `String str = new String()` – Baldy Jul 30 '14 at 23:14
  • 1
    public String() { this.value = new char[0]; } – Leo Jul 30 '14 at 23:16
  • 1
    public String(String original) { this.value = original.value; this.hash = original.hash; } – Leo Jul 30 '14 at 23:17
  • 4
    @Baldy No, that's completely wrong. `String str = "";` is distinctly different from `String str = new String("");` due to interning. – Kayaman Jul 30 '14 at 23:17
  • whats the difference in execution time? – eldjon Jul 30 '14 at 23:25
  • Are you saying that a single instantiation takes 3000 ns? – Patrick Collins Jul 31 '14 at 00:04
  • @PatrickCollins I think I am, yes. At least that is what the console in Eclipse is telling me =) – Roger Jul 31 '14 at 00:07
  • Of course I guess the actual amount of time elapsed is relative to hardware and workload etc. but the main point of my question lies in the differences though (which should still be virtually the same). – Roger Jul 31 '14 at 00:14
  • How many times have you run it? Are the result always the same (Which is longer than which)? – Anton Aug 01 '14 at 19:37

3 Answers3

3

It's hard to say without knowing what the results of your benchmarks, but I'd put my money on System.nanoTime not having a high enough resolution to measure a single object instantiation. I'd bet that both String() and String("") take less than one tick of nanoTime's clock to instantiate, and your result is an artifact of the fact that you're trying to measure something smaller than it's resolution.

This (three year old) question points towards ~20 ns for an instantiation, and the previous question I linked suggests that the resolution of nanoTime is actually ~10ns. My guess is that your instantiation time is actually somewhere between 10 ns and 20 ns for both, and what appears to be a difference is just noise.

EDIT In response to the comments:

Something is wrong. A single object instantiation can't possibly take 3000 ns. I'm guessing that you're measuring JVM warmup time or similar, and maybe the presence of "" is causing the JVM to hit some codepath it doesn't hit without "". I'm not sure what's causing your issue, but I don't think that the thing you are measuring is object instantiation time.

Community
  • 1
  • 1
Patrick Collins
  • 10,306
  • 5
  • 30
  • 69
  • So, assuming that you're right, the results I get from Scenario 1 would be correct as it breaks the timeframe of nanoTimes resolution? However, my benchmarks were always the same (i.e. around 5000ns for String() and around 3000ns for String(""). – Roger Jul 30 '14 at 23:50
  • Scenario 1 should be okay, because it's not trying to work in a unit of time that's smaller than `nanoTime`'s resolution. I don't know why `String()` would perform differently than `String("")`, but I'm trying to explain the difference between scenario 1 and 2. What measurement gave you 5000 and 3000 ns? – Patrick Collins Jul 30 '14 at 23:53
  • For scenario 2, *both* String() and String(""), when run many times consecutevily in a row, in the end comes down to 384ns. This might have to do with the "JVM warmup" you mentioned. But it opens up a new question - why is this "optimization"/warmup not visible in Scenario 1? Internals of the compiler doesn't optimize for-loops, maybe? – Roger Jul 31 '14 at 15:44
  • Ah, exactly, that's it then. JVM warmup is the time it takes for the JVM's optimizing compiler to rewrite the bytecode you're executing to be more efficient: when you run many iterations of a for loop, it has plenty of time, but over one single object instantiation, it can't do much. Running it repeatedly gave it the chance to optimize. – Patrick Collins Jul 31 '14 at 18:46
  • In that case the time of String() and String("") in Scenario 1 should be equal, shouldn't it? The time it takes to initialize a String with an original input String (it doesn't matter if there is anything in between the apostrophes... i.e. time "" == time "something") is lower than it takes to initialize a String with no input. It's cheaper (time-wise) to create a String() than to create a String("") in a for-loop. I'm sorry, but I am still a bit confused. – Roger Jul 31 '14 at 20:14
  • @Roger The JVM is dark magic, at least as far as I'm concerned. There is some deeply hidden implementation detail at work here. I don't know what it is, but it makes sense to me that a string literal hits a different (possibly more expensive) codepath than a regular object initialization, before optimization is applied. After it's optimized it behaves as expected. You're seeing really really low-level implementation-specific undocumented code of your particular JVM at work, it's anyone's guess how it works. – Patrick Collins Aug 01 '14 at 20:02
1

Okay, i'm submitting this as a new answer, because it really is different than my other one, and there's not enough room in the comments.

What you're seeing is the lack of accuracy in System.nanoTime as described here: Precision vs. accuracy of System.nanoTime().

After running many iterations of the same tests (with proper warm up) I've determined the following (for my machine)

  • The elapsed time for new String() is between 3.7 and 10.4ns with an average around 4.1
  • The elapsed time for new String("") is between 6.0 and 14.8ns with an average around 6.3
  • Single calls to new String() and new String("") both return timings of either 0ns or 307ns, with an occasional 306ns.

This basically proves Patrick's answer is correct. The precision of the nano clock doesn't really go low enough to measure a single call. A precise value can only be determined as an average across multiple calls. On my machine the precision appears to be ~300ns.

If you were somehow able to measure a single call, i strongly suspect it would show that new String() compared to new String("") has the same difference regardless of how many times you call it.

Community
  • 1
  • 1
Ted Bigham
  • 4,237
  • 1
  • 26
  • 31
0

The JVM can do runtime optimizations. In many/most cases you will only notice them where there's a high number of iterations. In scenario 1, it's likely one is getting optimized differently than the other.

Try running with runtime optimizations disabled and see you you still get conflicting results.

Ted Bigham
  • 4,237
  • 1
  • 26
  • 31
  • I disabled JIT with `-Djava.compiler=NONE` and got roughly the same results (apart from both scenario 2 loops taking longer time). Or maybe I need to do something else to disable runtime optimization? – Roger Jul 30 '14 at 23:37
  • You may also be running into the case where "you have to build an entire factory before you get a single can of beans from it". Doing something once causes the initialization to be a higher percentage of the overall cost compared to doing something 3000000 times (still only one initialization). You can try "warming up" your tests by having them in the code twice, but only measure the second time it runs. That way any initialization will have been done in the first (unmeasured) code. – Ted Bigham Jul 31 '14 at 00:06
  • Thanks for your input, Ted. Yes, I actually did this test too. Both String() and String("") in Scenario 2 in the end comes down to 384ns after a few consecutive initializations. However, this only proves that some optimization is taking place (or in your terms "the factory was started"). Also it opens up a new question - why does this optimization not occur in Scenario 1? This might be the phenomenon that Patrick mentioned in his post - "JVM warmup". – Roger Jul 31 '14 at 15:38