Scala 中的高效字符串连接
Posted
技术标签:
【中文标题】Scala 中的高效字符串连接【英文标题】:Efficient string concatenation in Scala 【发布时间】:2014-09-02 16:36:39 【问题描述】:JVM 使用+
优化字符串连接并将其替换为StringBuilder
。这在 Scala 中应该是一样的。但是如果字符串与++=
连接会发生什么?
var x = "x"
x ++= "y"
x ++= "z"
据我所知,这种方法将字符串视为字符序列,因此即使 JVM 会创建一个 StringBuilder
,它也会导致许多方法调用,对吧?改用 StringBuilder 会更好吗?
String 隐式转换成什么类型?
【问题讨论】:
你能不接受我的回答并选择 som-snytt 或 Rex Kerr 吗? 【参考方案1】:所用时间存在巨大的巨大差异。
如果您使用+=
重复添加字符串,您不会优化O(n^2)
创建增量更长字符串的成本。因此,添加一两个您不会看到差异,但它不会扩展;当您添加 100 个(短)字符串时,使用 StringBuilder 的速度会快 20 倍以上。 (精确数据:1.3 us 与 27.1 us 添加数字 0 到 100 的字符串表示;计时应该可重现到大约 += 5%,当然适用于我的机器。)
在var
String
上使用++=
更糟糕,因为您随后指示Scala 将字符串视为逐字符的集合,然后需要各种包装器来制作字符串看起来像一个集合(包括使用通用版本的++
!)。现在你在 100 次加法上又慢了 16 倍! (精确数据:428.8 us for ++=
on a var string 而不是 +=
的 26.7 us。)
如果你用一堆+
es 编写一个语句,那么 Scala 编译器将使用 StringBuilder 并最终得到一个高效的结果(数据:从数组中提取的非常量字符串为 1.8 us)。
因此,如果您在行中添加除+
以外的任何字符串,并且您关心效率,请使用StringBuilder
。绝对不要使用++=
将另一个String
添加到var
String
;没有任何理由这样做,而且运行时的损失很大。
(注意——通常你根本不关心你的字符串添加的效率有多高!除非你有理由怀疑这个特定的代码路径被称为很多。)
【讨论】:
我懒得去计时,但我不相信++=
是一个字符一个字符的; @om-nom-nom 关于“普通旧附加”是正确的,因为 TraversableLike 的 ++ 代表了构建器。也许并非总是如此;和 2.10 之前一样。
我最喜欢的 Odersky 提交消息(在 TraversableLike 上):Massive redesign so that: scala> "hi" == "hi".reverse.reverse
。这听起来几乎像paulp一样。 (我应该说 TL 总是使用构建器的 ++=,并且 StringBuilder 在 2.10 中覆盖了它。)
@som-snytt - StringBuilder
仅针对 String
覆盖 ++=
,但这里它被键入为 Builder[Char, String]
,因此它使用通用路径 - 无论如何它都传递了一个参数它只知道是GenTraversableOnce[Char]
,所以它会经过整个未优化的路径。
@deamon - 我做了基准测试。请参阅我的帖子中的时间安排。
好的,谢谢。现在我得拿出我的特殊眼镜再看一遍。【参考方案2】:
实际上,不方便的事实是StringOps
通常仍然是一个分配:
scala> :pa
// Entering paste mode (ctrl-D to finish)
class Concat
var x = "x"
x ++= "y"
x ++= "z"
// Exiting paste mode, now interpreting.
defined class Concat
scala> :javap -prv Concat
Binary file Concat contains $line3.$read$$iw$$iw$Concat
Size 1211 bytes
MD5 checksum 1900522728cbb0ed0b1d3f8b962667ad
Compiled from "<console>"
public class $line3.$read$$iw$$iw$Concat
SourceFile: "<console>"
[snip]
public $line3.$read$$iw$$iw$Concat();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=6, locals=1, args_size=1
0: aload_0
1: invokespecial #19 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #20 // String x
7: putfield #10 // Field x:Ljava/lang/String;
10: aload_0
11: new #22 // class scala/collection/immutable/StringOps
14: dup
15: getstatic #28 // Field scala/Predef$.MODULE$:Lscala/Predef$;
18: aload_0
19: invokevirtual #30 // Method x:()Ljava/lang/String;
22: invokevirtual #34 // Method scala/Predef$.augmentString:(Ljava/lang/String;)Ljava/lang/String;
25: invokespecial #36 // Method scala/collection/immutable/StringOps."<init>":(Ljava/lang/String;)V
28: new #22 // class scala/collection/immutable/StringOps
31: dup
32: getstatic #28 // Field scala/Predef$.MODULE$:Lscala/Predef$;
35: ldc #38 // String y
37: invokevirtual #34 // Method scala/Predef$.augmentString:(Ljava/lang/String;)Ljava/lang/String;
40: invokespecial #36 // Method scala/collection/immutable/StringOps."<init>":(Ljava/lang/String;)V
43: getstatic #28 // Field scala/Predef$.MODULE$:Lscala/Predef$;
46: invokevirtual #42 // Method scala/Predef$.StringCanBuildFrom:()Lscala/collection/generic/CanBuildFrom;
49: invokevirtual #46 // Method scala/collection/immutable/StringOps.$plus$plus:(Lscala/collection/GenTraversableOnce;Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;
52: checkcast #48 // class java/lang/String
55: invokevirtual #50 // Method x_$eq:(Ljava/lang/String;)V
在this answer查看更多演示。
编辑:多说,您在每次重新分配时都构建字符串,所以,不,您没有使用单个 StringBuilder
。
但是,优化是由javac
而不是JIT编译器完成的,所以比较同类的果实:
public class Strcat
public String strcat(String s)
String t = " hi ";
String u = " by ";
return s + t + u; // OK
public String strcat2(String s)
String t = s + " hi ";
String u = t + " by ";
return u; // bad
而
$ scala
Welcome to Scala version 2.11.2 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_11).
Type in expressions to have them evaluated.
Type :help for more information.
scala> :se -Xprint:typer
scala> class K def f(s: String, t: String, u: String) = s ++ t ++ u
[[syntax trees at end of typer]] // <console>
def f(s: String, t: String, u: String): String = scala.this.Predef.augmentString(scala.this.Predef.augmentString(s).++[Char, String](scala.this.Predef.augmentString(t))(scala.this.Predef.StringCanBuildFrom)).++[Char, String](scala.this.Predef.augmentString(u))(scala.this.Predef.StringCanBuildFrom)
不好。或者,更糟糕的是,展开 Rex 的解释:
"abc" ++ "def"
augmentString("abc").++[Char, String](augmentString("def"))(StringCanBuildFrom)
collection.mutable.StringBuilder.newBuilder ++= new WrappedString(augmentString("def"))
val b = collection.mutable.StringBuilder.newBuilder
new WrappedString(augmentString("def")) foreach b.+=
正如 Rex 所解释的,StringBuilder
会覆盖 ++=(String)
,但不会覆盖 Growable.++=(Traversable[Char])
。
如果您想知道unaugmentString
的用途:
28: invokevirtual #40 // Method scala/Predef$.augmentString:(Ljava/lang/String;)Ljava/lang/String;
31: invokevirtual #43 // Method scala/Predef$.unaugmentString:(Ljava/lang/String;)Ljava/lang/String;
34: invokespecial #46 // Method scala/collection/immutable/WrappedString."<init>":(Ljava/lang/String;)V
并且只是为了表明您最终确实调用了朴素的+=(Char)
,但在装箱和拆箱之后:
public final scala.collection.mutable.StringBuilder apply(char);
flags: ACC_PUBLIC, ACC_FINAL
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: getfield #19 // Field b$1:Lscala/collection/mutable/StringBuilder;
4: iload_1
5: invokevirtual #24 // Method scala/collection/mutable/StringBuilder.$plus$eq:(C)Lscala/collection/mutable/StringBuilder;
8: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this L$line10/$read$$iw$$iw$$anonfun$1;
0 9 1 x C
LineNumberTable:
line 9: 0
public final java.lang.Object apply(java.lang.Object);
flags: ACC_PUBLIC, ACC_FINAL, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: invokestatic #35 // Method scala/runtime/BoxesRunTime.unboxToChar:(Ljava/lang/Object;)C
5: invokevirtual #37 // Method apply:(C)Lscala/collection/mutable/StringBuilder;
8: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this L$line10/$read$$iw$$iw$$anonfun$1;
0 9 1 v1 Ljava/lang/Object;
LineNumberTable:
line 9: 0
开怀大笑确实会为血液注入氧气。
【讨论】:
以上是关于Scala 中的高效字符串连接的主要内容,如果未能解决你的问题,请参考以下文章