java - 单行字符串连接的速度差异

标签 java string profiling append timing

所以我一直lead to believe使用“+”运算符在一行中追加字符串与使用 StringBuilder 一样高效(而且绝对更美观)。今天,虽然我在使用 append 变量和字符串的记录器时遇到了一些速度问题,但它使用的是“+”运算符。所以我做了一个快速 test case令我惊讶的是,我发现使用 StringBuilder 更快!

基础知识是我使用 4 种不同的方法(如下所示)对每个追加次数平均使用 20 次运行。

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

                                               # of Appends
                           10^1    10^2    10^3    10^4     10^5     10^6       10^7
StringBuilder(capacity)    0.65    1.25    2       11.7     117.65   1213.25    11570
StringBuilder()            0.7     1.2     2.4     12.15    122      1253.7     12274.6
"+" operator               0.75    0.95    2.35    12.35    127.2    1276.5     12483.4
String.format              4.25    13.1    13.25   71.45    730.6    7217.15    -

Graph of percentage Difference from the fastest algorithm.

% Difference in String timings

I've checked out the byte code, it's different for each string comparison method.

Here is what I'm using for the methods, and you can see the whole test class here.

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);
}

我现在尝试使用 float 、整数和字符串。所有这些都或多或少显示出相同的时差。

问题

  1. “+”运算符显然不是成为相同的字节码,而且时间与最优的相差很大。那么给出了什么?
  2. 算法在 100 到 10000 次追加之间的行为对我来说很奇怪,所以有人有解释吗?

最佳答案

关于您的测试用例,我有两点不喜欢。首先,您在同一个进程中运行所有测试。在处理“大”(我知道模棱两可)时,但在处理您的进程如何与内存交互是您主要关注的任何事情时,您应该始终在单独的运行中进行基准测试。只是我们启动垃圾收集这一事实可能会影响早期运行的结果。你计算结果的方式让我有点困惑。我所做的是让每一个都单独运行,并将我运行它的次数减为零。我还让它运行多个“代表”,为每个代表计时。然后打印出每次运行所花费的毫秒数。这是我的代码:

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

正如您从我的结果中看到的那样,对于处于“中间范围”的值,每个连续的代表都会变得更快。我相信,这可以通过 JVM 开始运行并获取所需的内存来解释。随着“大小”的增加,此效果不允许接管,因为垃圾收集器无法释放太多内存,并且进程无法重新锁定。此外,当您执行这样的“重复”基准测试时,当您的大部分进程可以存在于较低级别的缓存中而不是 RAM 中时,您的进程对分支预测器更加敏感。这些非常聪明,可以捕捉到您的进程正在做什么,我想 JVM 会放大这一点。这也有助于解释为什么初始循环的值较慢,以及为什么您接近基准测试的方式是一个糟糕的解决方案。这就是为什么我认为您的值不是“大”的结果是有偏差的并且看起来很奇怪。然后,随着基准测试的“内存占用量”增加,此分支预测的影响(百分比方面)比您 append 的大字符串在 RAM 中移动的效果要小。

简单的结论:“大”运行的结果相当有效,看起来与我的相似(虽然我仍然不完全理解你是如何得到结果的,但相比之下百分比似乎排列得很好)。但是,由于测试的性质,您的较小运行结果无效。

关于java - 单行字符串连接的速度差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16722483/

相关文章:

java - 在 Axis2 中使用非合并解析器

python - python中字符串的终止符

java - 如何在 Eclipse 探查器中显示 java.lang.* 对象分配?

ruby - 如何获取另一个线程的调用堆栈?

php - 在 PHP 中分析不仅仅是函数调用

TCP 服务器上的 java.lang.ClassNotFoundException

java - JSON 变量名称中包含空格

Java 我该如何解析这个?

c - 使用 strcat() 的段错误

c++ - 代码在设置类变量时崩溃