18
Besides profiling this we have another possibility to get some insights. I want to focus on the possible speed differences and not on the things which remove them again.
除了剖析这一点,我们还有另一种可能获得一些见解。我想把注意力集中在可能的速度差异上,而不是把它们移走。
So lets start with this Test
class:
那么让我们从这个测试类开始:
public class Test {
// Do not optimize this
public static volatile String A = "A String";
public static void main( String [] args ) throws Exception {
String a1 = A + "B";
String a2 = A + 'B';
a1.equals( a2 );
}
}
I compiled this with javac Test.java (using javac -v: javac 1.7.0_55)
我用javac测试编译了这个。java(使用javac -v: javac 1.7.0_55)
Using javap -c Test.class we get:
使用javap - c测试。类我们得到:
Compiled from "Test.java"
public class Test {
public static volatile java.lang.String A;
public Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."":()V
7: getstatic #4 // Field A:Ljava/lang/String;
10: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
13: ldc #6 // String B
15: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
21: astore_1
22: new #2 // class java/lang/StringBuilder
25: dup
26: invokespecial #3 // Method java/lang/StringBuilder."":()V
29: getstatic #4 // Field A:Ljava/lang/String;
32: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
35: bipush 66
37: invokevirtual #8 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
40: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
43: astore_2
44: aload_1
45: aload_2
46: invokevirtual #9 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
49: pop
50: return
static {};
Code:
0: ldc #10 // String A String
2: putstatic #4 // Field A:Ljava/lang/String;
5: return
}
We can see, that there are two StringBuilders involved (Lines 4, 22 ). So the first thing we discover is, that using +
to concat Strings
is effectively the same as using StringBuilder.
我们可以看到,有两个stringbuilder(第4行,第22行)。我们发现的第一件事是,使用+ to concat字符串实际上和使用StringBuilder是一样的。
The second thing we can see here is that the StringBuilders both are called twice. First for appending the volatile variable (Lines 10, 32) and the second time for appending the constant part (Lines 15, 37)
我们可以看到的第二件事是,StringBuilders都被调用了两次。第一个用于附加volatile变量(第10行、第32行)和第二次附加常量部分(第15、37行)
In case of A + "B"
append
is called with a Ljava/lang/String
(a String) argument while in case of A + 'B'
it is called with an C
(a char) argument.
如果使用Ljava/lang/String (String)参数来调用A +“B”,则使用C (char)参数来调用。
So the compile does not convert String to char but leaves it as it is*.
因此,编译器不会将字符串转换为char,而是将其保留为*。
Now looking in AbstractStringBuilder
which contains the methods used we have:
现在看看AbstractStringBuilder,它包含了我们使用的方法:
public AbstractStringBuilder append(char c) {
ensureCapacityInternal(count + 1);
value[count++] = c;
return this;
}
and
和
public AbstractStringBuilder append(String str) {
if (str == null) str = "null";
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
as the methods actually called.
实际上是调用方法。
The most expensive operations here is certainly ensureCapacity
but only in case the limit is reached (it does an array copy of the old StringBuffers char[] into a new one). So this is true for both and makes no real difference.
这里最昂贵的操作当然是ensurecacity,但只有在达到极限时(它会将旧的stringbuffer char[]的数组复制到一个新的)中。所以这两种情况都是成立的,没有什么区别。
As one can see there are numerous other operations which are done but the real distinction is between value[count++] = c;
and str.getChars(0, len, value, count);
我们可以看到还有许多其他的操作,但真正的区别在于价值[count++] = c;和str.getChars(0, len, value, count);
If we look in to getChars we see, that it all boils down to one System.arrayCopy
which is used here to copy the String to the Buffer's array plus some checks and additional method calls vs. one single array access.
如果我们看一下getChars,我们会看到,所有这些都归结为一个系统。这里使用的arrayCopy将字符串复制到缓冲区的数组中,加上一些检查和额外的方法调用vs.一个数组访问。
So I would say in theory using A + "B"
is much slower than using A + 'B'
.
所以在理论上,A + B比A + B要慢得多。
I think in real execution it is slower, too. But to determine this we need to benchmark.
我认为在实际执行过程中,它也比较慢。但要确定这一点,我们需要进行基准测试。
EDIT: Of cause this is all before the JIT does it's magic. See Stephen C's answer for that.
编辑:因为这都是在JIT之前的魔法。参见Stephen C的答案。
EDIT2: I've been looking at the bytecode which eclipse's compiler generated and it's nearly identical. So at least these two compilers don't differ in the outcome.
EDIT2:我一直在关注eclipse编译器生成的字节码,它几乎是相同的。所以至少这两个编译器在结果上没有区别。
EDIT2: AND NOW THE FUN PART
现在是有趣的部分。
The Benchmarks. This result is generated by running Loops 0..100M for a+'B'
and a+"B"
few times after a warmup:
基准。此结果由运行循环0生成。a+'B'和a+"B"在热身后几次:
a+"B": 5096 ms
a+'B': 4569 ms
a+'B': 4384 ms
a+"B": 5502 ms
a+"B": 5395 ms
a+'B': 4833 ms
a+'B': 4601 ms
a+"B": 5090 ms
a+"B": 4766 ms
a+'B': 4362 ms
a+'B': 4249 ms
a+"B": 5142 ms
a+"B": 5022 ms
a+'B': 4643 ms
a+'B': 5222 ms
a+"B": 5322 ms
averageing to:
平均:
a+'B': 4608ms
a+"B": 5167ms
So even in the real benchmark world of syntetic knowlege (hehe) a+'B'
is about 10% faster than a+"B"
...
因此,即使是在真正的标准知识世界中,a+ B的速度也比a+ B快10%……
... at least (disclaimer) on my system with my compiler and my cpu and it's really no difference / not noticeable in real world programms. Except of cause you have a piece of code you run realy often and all your application perfomance depends on that. But then you would probably do things different in the first place.
…至少(免责声明)在我的系统上有我的编译器和我的cpu,在现实世界的程序中没有明显的区别。除了因为你有一段代码你经常运行,你的所有应用程序性能都取决于这个。但首先你可能会做一些不同的事情。
EDIT4:
标:
Thinking about it. This is the loop used to benchmark:
思考这个问题。这是用于基准测试的循环:
start = System.currentTimeMillis();
for( int i=0; i
so we're really not only benchmarking the one thing we care about but although java loop performance, object creation perfomance and assignment to variables performance. So the real speed difference may be even a little bigger.
因此,我们不仅要对我们关心的一件事进行基准测试,而且还要考虑java循环性能、对象创建性能和变量性能的分配。所以真正的速度差异可能会更大。