2016-10-26 23 views
7

我知道在最近的Java版本字符串连接Java使用+优化字符串连接多少?

String test = one + "two"+ three; 

将得到优化使用StringBuilder

但是,每次它到达这一行时,是否会生成一个新的StringBuilder,或者是否会生成一个随后用于所有字符串连接的单个Thread Local StringBuilder?

换句话说,我可以通过创建自己的线程本地StringBuilder来重新使用还是不会有显着的收益,从而改进经常调用的方法的性能?

我可以只写一个测试,但我不知道它可能是编译器/ JVM特定的或可以更普遍地回答?

+1

在连接表达式时要小心重入。 – SLaks

+1

最后我检查它是相当愚蠢的,强制'StringBuilder'反复重新分配。但是,这是Oracle的JDK专用的,查看得到的字节码,因此没有考虑JVM可能做的任何优化。我的规则是:99.999%的时间你不关心,当然;对于您关心的.001%,请使用分配足够大的字符串以显式处理总体结果。 –

+0

除非你正在做的更多的字符串操作,而不仅仅是那一行,我同意T.J .: 99.999%的时间你不会看到任何区别。 JVM实际上将所有内存分配为线程本地的(直到它需要与另一个线程共享),iiuc,所以你的线程本地可能不会有任何好处。 – markspace

回答

6

据我所知,没有编译器生成代码重用StringBuilder实例,最显着javac和ECJ不生成重用代码。

重要的是要强调,不要做这种重复使用是合理的。假设从ThreadLocal变量检索实例的代码比从TLAB进行普通分配的代码更快是不安全的。即使通过试图增加本地gc循环的潜在成本来回收该实例,就我们可以确定其在成本中的比例而言,我们也不能得出这样的结论。

因此,试图重用构建器的代码会更复杂,浪费内存,因为它会让构建器保持活跃状态​​,而不知道它是否会实际重用,而没有明确的性能优势。

尤其是当我们考虑到另外上面

  • 的JVM像热点的声明有逃逸分析,它可以完全的Elid纯本地分配这样的,也可能的Elid阵列的复制成本调整操作
  • 这种复杂的JVM通常还具有专门用于StringBuilder基于级联的优化,其工作时最好编译后的代码如下常见图案

随着Java 9,图片将再次发生变化。然后,字符串连接将被编译为invokedynamic指令,该指令将在运行时链接到JRE提供的工厂(请参阅StringConcatFactory)。然后,JRE将决定代码的外观,如果它对特定的JVM有好处,它可以将它定制到特定的JVM,包括缓冲区重用。这也将减少代码的大小,因为它只需要一条指令,而不是分配的顺序和对StringBuilder的多个调用。

+1

与jdk-9的图片变化*戏剧性*再次:) – Eugene

6

你会为jdk-9字符串连接付出多少努力感到惊讶。第一个javac发出一个invokedynamic而不是调用StringBuilder#append。该invokedynamic将返回一个CallSite并包含一个MethodHandle(实际上是一系列MethodHandle)。

因此,将实际完成的字符串连接的决定移至运行时。不利的一面是,你第一次连接将会变慢的字符串(对于相同类型的参数)。

然后有一系列的策略,你可以从连接字符串时选择(可以一个通过java.lang.invoke.stringConcat参数替代默认):

private enum Strategy { 
    /** 
    * Bytecode generator, calling into {@link java.lang.StringBuilder}. 
    */ 
    BC_SB, 

    /** 
    * Bytecode generator, calling into {@link java.lang.StringBuilder}; 
    * but trying to estimate the required storage. 
    */ 
    BC_SB_SIZED, 

    /** 
    * Bytecode generator, calling into {@link java.lang.StringBuilder}; 
    * but computing the required storage exactly. 
    */ 
    BC_SB_SIZED_EXACT, 

    /** 
    * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. 
    * This strategy also tries to estimate the required storage. 
    */ 
    MH_SB_SIZED, 

    /** 
    * MethodHandle-based generator, that in the end calls into {@link java.lang.StringBuilder}. 
    * This strategy also estimate the required storage exactly. 
    */ 
    MH_SB_SIZED_EXACT, 

    /** 
    * MethodHandle-based generator, that constructs its own byte[] array from 
    * the arguments. It computes the required storage exactly. 
    */ 
    MH_INLINE_SIZED_EXACT 
} 

默认的策略是:MH_INLINE_SIZED_EXACT这是一个兽!

它采用包私有构造函数建立字符串(这是最快的):

/* 
* Package private constructor which shares value array for speed. 
*/ 
String(byte[] value, byte coder) { 
    this.value = value; 
    this.coder = coder; 
} 

首先这一战略将创建所谓的过滤器;这些基本上都是将传入参数转换为字符串值的方法句柄。正如人们所预料的,这些MethodHandles都存储在一个名为Stringifiers类,在大多数情况下产生MethodHandle调用:

String.valueOf(YourInstance) 

所以,如果你有,你想连接3个对象将有3个MethodHandles将委托到String.valueOf(YourObject)这实际上意味着你已经将你的对象转换成了字符串。 这个班里面有一些我仍然无法理解的调整,像需要分开的类StringifierMost(转换为仅字符串引用,浮动和双精度)和StringifierAny

由于MH_INLINE_SIZED_EXACT表示字节数组被计算为精确的大小;有一种计算方法。

这样做的方式是通过StringConcatHelper#mixLen中的方法获取输入参数的字符串化版本(References/float/double)。此时我们知道最终字符串的大小。那么,我们实际上并不知道它,我们有一个MethodHandle将会计算它。

String jdk-9还有一个值得一提的更改 - 增加了一个coder字段。这是计算字符串的大小/相等/字符所需的。既然它需要大小,我们也需要计算它;这是通过StringConcatHelper#mixCoder完成的。

正是在这一点上安全地委派MethodHandle,将创建乌尔数组:

@ForceInline 
    private static byte[] newArray(int length, byte coder) { 
     return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder); 
    } 

如何每个元素追加?通过StringConcatHelper#prepend中的方法。

只有现在我们需要调用需要一个字节的字符串的构造函数所需的所有细节。


所有这些操作(和许多其他我都跳过为简单起见)通过时发出的追加实际发生,将调用的MethodHandle处理。

+1

我在这个答案中被带走了一点,原因很简单,这样一个简单的操作的细节是令人着迷的国际海事组织。 – Eugene

+0

这真的很有趣,但不幸的是它并不真的直接回答这个问题 - 所以我觉得我不能移动滴答:( –

+1

@TimB完全同意,这不是滴答:)。接受的答案是正确的。 – Eugene