Java如何使用+;优化字符串连接;?
我知道在最近的Java版本中,字符串连接Java如何使用+;优化字符串连接;?,java,string,optimization,stringbuilder,string-concatenation,Java,String,Optimization,Stringbuilder,String Concatenation,我知道在最近的Java版本中,字符串连接 String test = one + "two"+ three; 将得到优化以使用StringBuilder 但是,是否会在每次点击此行时生成一个新的StringBuilder,还是会生成一个单线程本地StringBuilder,然后用于所有字符串连接 换句话说,我可以通过创建自己的线程本地StringBuilder来提高频繁调用的方法的性能以供重用,还是这样做不会有显著的好处 我可以为此编写一个测试,但我想知道它可能是特定于编译器/JVM的,还是可
String test = one + "two"+ three;
将得到优化以使用StringBuilder
但是,是否会在每次点击此行时生成一个新的StringBuilder
,还是会生成一个单线程本地StringBuilder,然后用于所有字符串连接
换句话说,我可以通过创建自己的线程本地StringBuilder来提高频繁调用的方法的性能以供重用,还是这样做不会有显著的好处
我可以为此编写一个测试,但我想知道它可能是特定于编译器/JVM的,还是可以更一般地回答的?据我所知,没有编译器生成代码重用
StringBuilder
实例,最明显的是javac
和ECJ不生成重用代码
重要的是要强调,不重复使用是合理的。假设从ThreadLocal
变量检索实例的代码比从TLAB进行普通分配要快是不安全的。即使尝试添加回收该实例的本地gc循环的潜在成本,只要我们能够确定其在成本中所占的比例,我们也无法得出结论
因此,试图重用构建器的代码将更加复杂,浪费内存,因为它使构建器保持活力,而不知道它是否会被实际重用,也没有明显的性能优势
特别是当我们考虑上述语句
时- 像HotSpot这样的JVM都有Escape分析,它可以完全省去像这样的纯本地分配,还可以省去数组大小调整操作的复制成本
- 这种复杂的JVM通常也有专门用于基于
的连接的优化,当编译的代码遵循公共模式时,这种优化效果最好StringBuilder
使用Java9,情况将再次发生变化。然后,字符串连接将被编译为
invokedynamic
指令,该指令将在运行时链接到JRE提供的工厂(请参阅)。然后,JRE将决定代码的外观,这允许根据特定的JVM对代码进行定制,包括缓冲区重用,如果它对特定的JVM有好处的话。这还将减少代码大小,因为它只需要一条指令,而不需要分配序列和对StringBuilder
的多次调用。您会惊讶于jdk-9字符串连接付出了多少努力。第一个javac发出一个invokedynamic
,而不是对StringBuilder\append
的调用。invokedynamic将返回一个CallSite
,其中包含一个MethodHandle(实际上是一系列MethodHandles)
因此,字符串连接的实际操作决定被移动到运行时。缺点是第一次连接字符串时速度会慢一些(对于相同类型的参数)
然后,在连接字符串时,可以选择一系列策略(可以通过java.lang.invoke.stringConcat
参数覆盖默认策略):
默认策略是:MH\u INLINE\u size\u EXACT
,这是一个野兽强>
它使用包私有构造函数构建字符串(最快):
首先,该策略创建所谓的过滤器;这些基本上是将传入参数转换为字符串值的方法句柄。正如人们所料,这些MethodHandle存储在一个名为Stringifiers
的类中,在大多数情况下,该类生成一个MethodHandle,该类调用:
String.valueOf(YourInstance)
因此,如果您有3个要连接的对象,那么将有3个MethodHandles委托给String.valueOf(YourObject)
,这实际上意味着您已将对象转换为字符串。
这个类中有一些我仍然无法理解的调整;就像需要有单独的类StringifierMost
(只转换为字符串引用、浮点和双精度)和StringifierAny
因为MH_INLINE_size_EXACT
表示字节数组计算为精确大小;有一种计算方法
这是通过StringConcatHelper#mixLen
中的方法实现的,这些方法获取输入参数的字符串化版本(References/float/double)。此时,我们知道最终字符串的大小。实际上我们并不知道,我们有一个方法句柄来计算它
字符串jdk-9中还有一个值得一提的变化——添加了coder
字段。这是计算字符串的大小/相等性/字符所必需的。因为尺寸需要它,我们也需要计算它;这是通过StringConcatHelper#mixCoder
完成的
此时可以安全地委托MethodHandle来创建ur数组:
@ForceInline
private static byte[] newArray(int length, byte coder) {
return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder);
}
@ForceInline
私有静态字节[]新数组(整数长度,字节编码器){
返回(字节[])不安全。分配InInitializeDarray(byte.class,length在连接表达式时要注意重入性。上次我检查时,它相当愚蠢,迫使StringBuilder
反复重新分配。但这是Oracle的JDK特有的,查看结果字节码,因此没有考虑JVM可能做的任何优化。我的规则是:99.999%的时间你不在乎当然,对于你关心的.001%,使用显式的StringBuilder
分配足够大的空间来处理整个结果。除非你做的字符串操作比那一行多得多,否则我同意T.J.:99.999%的时间你看不到任何差异。JVM实际上会将所有内存作为本地内存分配给线程(直到它需要与另一个线程共享),iiuc,所以您的线程
String.valueOf(YourInstance)
@ForceInline
private static byte[] newArray(int length, byte coder) {
return (byte[]) UNSAFE.allocateUninitializedArray(byte.class, length << coder);
}