Java 单行字符串连接的速度差

Java 单行字符串连接的速度差,java,string,profiling,append,timing,Java,String,Profiling,Append,Timing,因此,我一直认为使用“+”操作符在一行上附加字符串与使用StringBuilder一样有效(而且从外观上看肯定更好)。今天,虽然我遇到了一些附加变量和字符串的记录器的速度问题,但它使用了“+”操作符。所以我做了一个快速的测试,让我惊讶的是发现使用StringBuilder更快 我使用了4种不同的方法(如下所示),每个附录的平均运行次数为20次 结果,时间(以毫秒为单位) 我现在已经尝试了浮点、整数和字符串。所有这些都或多或少地显示出相同的时差 问题 “+”运算符显然不是相同的字节码,而且时间与最

因此,我一直认为使用“+”操作符在一行上附加字符串与使用StringBuilder一样有效(而且从外观上看肯定更好)。今天,虽然我遇到了一些附加变量和字符串的记录器的速度问题,但它使用了“+”操作符。所以我做了一个快速的测试,让我惊讶的是发现使用StringBuilder更快

我使用了4种不同的方法(如下所示),每个附录的平均运行次数为20次

结果,时间(以毫秒为单位)

我现在已经尝试了浮点、整数和字符串。所有这些都或多或少地显示出相同的时差

问题

  • “+”运算符显然不是相同的字节码,而且时间与最佳值相差很大。那么是什么原因呢
  • 在我看来,100到10000个附录之间的算法的行为是非常奇怪的,所以有人有解释吗

  • Java语言规范没有指定如何执行字符串连接,但我怀疑您的编译器除了执行以下等效操作外,是否还执行其他操作:

    new StringBuilder("[").
      append(a).
      append(",").
      append(b).
      append(",").
      append(c).
      append("][").
      append(x).
      append(",").
      append(y).
      append(",").
      append(z).
      append("]").
      toString();
    
    您可以使用“javap-c…”来反编译类文件并验证这一点


    如果度量方法之间在运行时的任何显著的重复性差异,我更愿意假设垃圾收集器在不同的时间运行,而不是存在任何实际的、显著的性能差异。创建具有不同初始容量的
    StringBuilder
    s当然可能会有一些影响,但与格式化浮动等所需的工作相比,这应该是微不足道的。

    我不喜欢您的测试用例有两个方面。首先,在同一进程中运行所有测试。当处理“大”(我知道不明确)时,但当处理进程如何与内存交互是您主要关心的问题时,您应该始终在单独的运行中进行基准测试。事实上,我们已经启动了垃圾收集,这可能会影响早期运行的结果。你计算结果的方式让我有点困惑。我所做的是每一次单独的跑步,并将我的跑步次数减为零。我还让它运行多次“重复”,对每次重复计时,然后打印出每次运行所用的毫秒数。这是我的密码:

    import java.util.Random;
    
    public class blah {
      public static void main(String[] args){
        stringComp();
        }
    
        private static void stringComp() {
            int SIZE = 1000000;
            int NUM_REPS = 5;
            for(int j = 0; j < NUM_REPS; j++) {
                Random r = new Random();
                float f;
                long start = System.currentTimeMillis();
                for (int i=0;i<SIZE;i++){
                    f = r.nextFloat();
                    stringSpeed3(f,f,f,f,f,f);
                }
                System.out.print((System.currentTimeMillis() - start));
                System.out.print(", ");
            }
        }
    
        public static String stringSpeed1(float a, float b, float c, float x, float y, float z){
            StringBuilder sb = new StringBuilder(72).append("[").append(a).append(",").append(b).append(",").append(c).append("][").
                    append(x).append(",").append(y).append(",").append(z).append("]");
            return sb.toString();
        }
    
        public static String stringSpeed2(float a, float b, float c, float x, float y, float z){
            StringBuilder sb = new StringBuilder().append("[").append(a).append(",").append(b).append(",").append(c).append("][").
                    append(x).append(",").append(y).append(",").append(z).append("]");
            return sb.toString();
        }
    
        public static String stringSpeed3(float a, float b, float c, float x, float y, float z){
            return "["+a+","+b+","+c+"]["+x+","+y+","+z+"]";
        }
    
        public static String stringSpeed4(float a, float b, float c, float x, float y, float z){
            return String.format("[%f,%f,%f][%f,%f,%f]", a,b,c,x,y,z);
        }
    
    }
    
    从我的结果中可以看出,在“中等范围”的值上,每次连续重复都会加快速度。我相信,这可以通过JVM运行并抓住它所需的内存来解释。随着“大小”的增加,不允许这种影响发生,因为有太多的内存让垃圾收集器无法释放,也让进程无法锁定。此外,当您在执行这样的“重复”基准测试时,当您的大多数进程可以存在于较低级别的缓存中而不是RAM中时,您的进程对分支预测器更加敏感。这些都是非常智能的,可以理解您的进程正在做什么,我想JVM会放大这一点。这也有助于解释为什么初始循环上的值较慢,以及为什么您接近基准测试的方式是一个糟糕的解决方案。这就是为什么我认为你对非“大”值的结果是扭曲的,而且看起来很奇怪。然后,随着基准测试的“内存占用”的增加,这个分支预测的效果(百分比)比您在RAM中添加的大字符串要小


    简化结论:你的“大”跑步结果是合理有效的,并且似乎与我的结果相似(尽管我仍然不完全理解你是如何得到结果的,但相比之下,百分比似乎吻合得很好)。但是,由于测试的性质,您对较小运行的结果是无效的。

    100到….之间的算法行为已修复,由于一些原因,它将其切断,并有研究和数据支持+1运行这些时,您是否在优化?如果没有优化,运算符和函数调用经常会在内存中表现异常,并导致在适当优化时没有的异常性能。因此,我在我的计算机Mac上使用标准默认编译<代码>java版本“1.6.0_45”。我没有使用任何特定的优化标志,我已经反编译了代码,“+”操作符与StringBuilder()不同,尽管我没有尝试使用StringBuilder(“[”);,我来看看,你能把javap的输出添加到你的问题中吗?我在问题中也链接了它。它们都非常相似,但在大容量运行时,少数差异似乎开始引起明显的差异。如果我不是完全错的话,根据字节码,你的stringSpeed5方法被实现为
    StringBuilder sb=new StringBuilder(“[”);return sb.toString();
    使用字符串连接的等效方法应该是
    string s=“[”+…;return s;
    。如果不使用局部变量,而是直接将第五个方法实现为
    return new StringBuilder(“[”)…toString();
    你应该得到相同的字节码。这看起来很神奇。在“新的StringBuilder([”)”;和直接返回之间,最终使字节码等效。这对我来说意味着两件事。1)另一个问题的答案仍然是错的,因为原始海报分配了一个新的StringBuilder(容量)并返回它,这不会导致等价的字节码。2)我很失望java编译器不够聪明,无法让它分配一个新对象并立即返回它只是返回它……我确信它会自动执行这一操作。我想你对分支预测器有点了解,我认为这是gc倾斜较小的运行,但我真的很喜欢您从运行1,2下降到3…这对我来说意味着数量。对于较大的运行,您显示了相同的效果,方法不等效您假设了许多假设h
    new StringBuilder("[").
      append(a).
      append(",").
      append(b).
      append(",").
      append(c).
      append("][").
      append(x).
      append(",").
      append(y).
      append(",").
      append(z).
      append("]").
      toString();
    
    import java.util.Random;
    
    public class blah {
      public static void main(String[] args){
        stringComp();
        }
    
        private static void stringComp() {
            int SIZE = 1000000;
            int NUM_REPS = 5;
            for(int j = 0; j < NUM_REPS; j++) {
                Random r = new Random();
                float f;
                long start = System.currentTimeMillis();
                for (int i=0;i<SIZE;i++){
                    f = r.nextFloat();
                    stringSpeed3(f,f,f,f,f,f);
                }
                System.out.print((System.currentTimeMillis() - start));
                System.out.print(", ");
            }
        }
    
        public static String stringSpeed1(float a, float b, float c, float x, float y, float z){
            StringBuilder sb = new StringBuilder(72).append("[").append(a).append(",").append(b).append(",").append(c).append("][").
                    append(x).append(",").append(y).append(",").append(z).append("]");
            return sb.toString();
        }
    
        public static String stringSpeed2(float a, float b, float c, float x, float y, float z){
            StringBuilder sb = new StringBuilder().append("[").append(a).append(",").append(b).append(",").append(c).append("][").
                    append(x).append(",").append(y).append(",").append(z).append("]");
            return sb.toString();
        }
    
        public static String stringSpeed3(float a, float b, float c, float x, float y, float z){
            return "["+a+","+b+","+c+"]["+x+","+y+","+z+"]";
        }
    
        public static String stringSpeed4(float a, float b, float c, float x, float y, float z){
            return String.format("[%f,%f,%f][%f,%f,%f]", a,b,c,x,y,z);
        }
    
    }
    
    stringSpeed1(SIZE = 10000000): 11548, 11305, 11362, 11275, 11279
    stringSpeed2(SIZE = 10000000): 12386, 12217, 12242, 12237, 12156
    stringSpeed3(SIZE = 10000000): 12313, 12016, 12073, 12127, 12038
    
    stringSpeed1(SIZE = 1000000): 1292, 1164, 1170, 1168, 1172
    stringSpeed2(SIZE = 1000000): 1364, 1228, 1230, 1224, 1223
    stringSpeed3(SIZE = 1000000): 1370, 1229, 1227, 1229, 1230
    
    stringSpeed1(SIZE = 100000): 246, 115, 115, 116, 113
    stringSpeed2(SIZE = 100000): 255, 122, 123, 123, 121
    stringSpeed3(SIZE = 100000): 257, 123, 129, 124, 125
    
    stringSpeed1(SIZE = 10000): 113, 25, 14, 13, 13
    stringSpeed2(SIZE = 10000): 118, 23, 24, 16, 14
    stringSpeed3(SIZE = 10000): 120, 24, 16, 17, 14
    
    //This run SIZE is very interesting.  
    stringSpeed1(SIZE = 1000): 55, 22, 8, 6, 4 
    stringSpeed2(SIZE = 1000): 54, 23, 7, 4, 3
    stringSpeed3(SIZE = 1000): 58, 23, 7, 4, 4
    
    stringSpeed1(SIZE = 100): 6, 6, 6, 6, 6 
    stringSpeed2(SIZE = 100): 6, 6, 5, 6, 6
    stirngSpeed3(SIZE = 100): 8, 6, 7, 6, 6