Scala中的高效字符串连接

dea*_*mon 16 performance scala

JVM优化字符串连接,+并用a替换它StringBuilder.这在Scala中应该是相同的.但是如果串连接会发生什么++=

var x = "x"
x ++= "y"
x ++= "z"
Run Code Online (Sandbox Code Playgroud)

据我所知,这个方法处理像char seqences这样的字符串,所以即使JVM创建StringBuilder它也会导致很多方法调用,对吧?使用StringBuilder会更好吗?

隐式转换的字符串是什么类型的?

Rex*_*err 14

有一个巨大的巨大及时采取差异.

如果使用重复添加字符串+=,则不要优化O(n^2)创建增量更长的字符串的成本.因此,对于添加一个或两个,您将看不到差异,但它不会扩展; 当你添加100(短)字符串时,使用StringBuilder的速度要快20倍.(精确数据:1.3 us对27.1我们添加数字0到100的字符串表示;时间应该可以重现到约+ = 5%,当然也适用于我的机器.)

使用++=var String又是远差远了,因为你然后指示斯卡拉治疗字符串作为一个字符一个字符集合,然后需要各种包装,使字符串看起来像一个集合(包括盒装字符一个字符另外使用通用版本++!).现在你再增加100倍就会慢16倍!(准确的数据:428.8 us用于++=var字符串而不是+=26.7 us.)

如果你用一堆+es 编写一个单独的语句,那么Scala编译器将使用一个StringBuilder并最终得到一个有效的结果(Data:1.8 us on非常量字符串从数组中拉出).

因此,如果您添加除了+行之外的任何字符串,并且您关心效率,请使用StringBuilder.绝对不要用++=另一个添加Stringvar String; 没有任何理由这样做,并且有很大的运行时间惩罚.

(注意 - 通常你根本不在乎你的字符串添加效率如何!StringBuilder除非你有理由怀疑这个特定的代码路径被大量调用,否则不要用额外的s 混乱你的代码.)


som*_*ytt 13

实际上,不方便的事实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
Run Code Online (Sandbox Code Playgroud)

此答案中查看更多演示.

编辑:更多的说,你在每次重新分配时都在构建String,所以,不,你没有使用单个StringBuilder.

但是,优化是由javacJIT编译器完成的,而不是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
    }
}
Run Code Online (Sandbox Code Playgroud)

$ 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)
Run Code Online (Sandbox Code Playgroud)

不好.或者更糟糕的是,要展开雷克斯的解释:

  "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.+=
Run Code Online (Sandbox Code Playgroud)

正如雷克斯所解释的那样,但是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
Run Code Online (Sandbox Code Playgroud)

并且只是为了表明你最后打电话,+=(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
Run Code Online (Sandbox Code Playgroud)

一个好笑会让一些氧气进入血液.