问题
I am trying some performance benchmark regarding String Pool. However, the outcome is not expected.
I made 3 static methods
- perform0() method ... creates a new object every time
- perform1() method ... String literal "Test"
- perform2() method ... String constant expression "Te"+"st"
My expectation was (1. fastest -> 3. slowest)
- "Test" because of string pooling
- "Te"+"st" because of string pooling but bit slower than 1 because of + operator
- new String(..) because of no string pooling.
But the benchmark shows that "Te"+"st" is slighty faster than "Test".
new String(): 141677000 ns
"Test" : 1148000 ns
"Te"+"st" : 1059000 ns
new String(): 141253000 ns
"Test" : 1177000 ns
"Te"+"st" : 1089000 ns
new String(): 142307000 ns
"Test" : 1878000 ns
"Te"+"st" : 1082000 ns
new String(): 142127000 ns
"Test" : 1155000 ns
"Te"+"st" : 1078000 ns
...
Here's the code:
import java.util.concurrent.TimeUnit;
public class StringPoolPerformance {
public static long perform0() {
long start = System.nanoTime();
for (int i=0; i<1000000; i++) {
String str = new String("Test");
}
return System.nanoTime()-start;
}
public static long perform1() {
long start = System.nanoTime();
for (int i=0; i<1000000; i++) {
String str = "Test";
}
return System.nanoTime()-start;
}
public static long perform2() {
long start = System.nanoTime();
for (int i=0; i<1000000; i++) {
String str = "Te"+"st";
}
return System.nanoTime()-start;
}
public static void main(String[] args) {
long time0=0, time1=0, time2=0;
for (int i=0; i<100; i++) {
// result
time0 += perform0();
time1 += perform1();
time2 += perform2();
}
System.out.println("new String(): " + time0 + " ns");
System.out.println("\"Test\" : " + time1 + " ns");
System.out.println("\"Te\"+\"st\" : " + time2 + " ns");
}
}
Can someone explain why "Te"+"st" performs faster than "Test"? Is JVM doing some optimizations here? Thank you.
回答1:
"Te" + "st"
is a compiler-time constant expression, and so will behave at runtime no differently than simply "Test"
. Any performance hit will be when trying to compile it, not when trying to run it.
That's easily proven by disassembling your compiled benchmark class using javap -c StringPoolPerformance
:
public static long perform1();
Code:
...
7: ldc #3; //int 1000000
9: if_icmpge 21
12: ldc #5; //String Test
14: astore_3
15: iinc 2, 1
...
public static long perform2();
Code:
...
7: ldc #3; //int 1000000
9: if_icmpge 21
12: ldc #5; //String Test
14: astore_3
15: iinc 2, 1
...
The methods' byte code are absolutely identical! This is specified by the Java Language Specification, 15.18.1:
The String object is newly created (§12.5) unless the expression is a compile-time constant expression (§15.28).
The benchmark difference you experience is probably due to typical variability or because your benchmark isn't perfect. See this question: How do I write a correct micro-benchmark in Java?
Some notable rules you break:
- You don't discard the results of the "warm-up" iterations of your test kernel.
- You don't have GC logging enabled (particularly relevant when
perform1()
is always being run right after the test which creates a million objects).
回答2:
Perhaps the JIT compiler kicks in and the third is executing native code. Perhaps the concatenation was moved outside the loop. Perhaps the concatenation is never done because the variable is never read. Perhaps the difference is noise and your three samples coincidentally point the same way.
Robust Java benchmarking, Part 1: Issues explains a lot of ways that benchmarking Java can go wrong.
Benchmarking is extremely difficult. Many factors, both obvious and subtle, can affect your results. To obtain accurate results, you need a thorough command of these issues, possibly by using a benchmarking framework that addresses some of them. Go to Part 2 to learn about just such a robust Java benchmarking framework.
Don't expect micro-benchmarks of Java code to tell you anything useful until you understand the specific pitfalls that the JVM architecture introduces, and don't expect that even the best micro-benchmarks predict the performance of a real application.
I don't know what your goal is, but learning to use a good profiler and using it on your actual application will usually tell you if the line in question is really the source of inefficiency and let you measure the effect of a code change. Time spent learning a profiler is probably better spent than time writing and debugging micro-benchmarks.
回答3:
First of all it would be nice to know that:
If you are concatenating Strings over and over, lets say in a loop, then you know that because they are immutable, new Strings keep getting generated. The javac compiler internally uses a StringBuffer to do this- so for example, you have
String itemList = "";
itemList=itemList + items[i].description;
in a loop.
What happens is, within the loop, two objects are generated. One is a StringBuffer-
itemList=new StringBuffer().append(itemList).
append(items[i].description).toString();
The other is the String that gets assigned to itemList via the toString().`
Source: http://thought-bytes.blogspot.com/2007/03/java-string-performance.html
I think that this doesn't apply to your case. In the first performance test you always create a new object so there are created 1000000 String("Test")
objects. In the second and third examples there is created only one object to which is pointed by many referenced. As was said before: "Te"+"st"
was treated as compiler-time constant and differences are too small to say that it's faster than "Test".
回答4:
Sorry to post an answer but I couldn't put this in a comment to show how flawed this benchmark is. On Linux I changed the order and get:
The order defiantly matters.
new String() : 123328907 ns
"Test" : 1153035 ns
"Te"+"st" : 5389377 ns
"a"+"b"+"c"+"d": 1256918 ns
回答5:
Mark Peters is right, two String constants will be concatenated without mercy.
This is because of the copy time needed to concatenate String objects, following their size.
Now these are compiled into StringBuffer/StringBuilder objects by the compiler, you can see it by decompiling a .class file.
You should take a look at these classes, but beware that when rendering a StringBuilder or StringBuffer as a String, a new String object will be created.
来源:https://stackoverflow.com/questions/12081170/string-pool-test-faster-than-test