【问题标题】:Is string concatenaion really that slow?字符串连接真的那么慢吗?
【发布时间】:2013-02-17 03:35:42
【问题描述】:

我目前正在研究字符串连接选项以及它们对整体性能的影响。我的测试用例产生的结果让我大吃一惊,我不确定我是否忽略了某些东西。

这里是交易:在 java 中执行 "something"+"somethingElse" 将(在编译时)在每次完成时创建一个新的 StringBuilder

对于我的测试用例,我正在从我的 HDD 加载一个文件,该文件包含 1661 行示例数据(经典的“Lorem Ipsum”)。这个问题不是关于 I/O 性能,而是关于不同字符串 concat 方法的性能。

public class InefficientStringConcat {

    public static void main(String[] agrs) throws Exception{
        // Get a file with example data:

        System.out.println("Starting benchmark");
        // Read an measure:
        for (int i = 0; i < 10; i++){
            BufferedReader in = new BufferedReader(
                    new InputStreamReader(new FileInputStream(new File("data.txt")))
            );

            long start = System.currentTimeMillis();
            // Un-comment method to test:
            //inefficientRead(in);
            //betterRead(in);
            long end = System.currentTimeMillis();
            System.out.println("Took "+(end-start)+"ms");

            in.close();
        }



    }

    public static String betterRead(BufferedReader in) throws IOException{
        StringBuilder b = new StringBuilder();
        String line;
        while ((line = in.readLine()) != null){
            b.append(line);
        }
        return b.toString();
    }

    public static String inefficientRead(BufferedReader in) throws IOException {
        String everything = "", line;
        while ((line = in.readLine()) != null){
            everything += line;
        }
        return everything;
    }
}

如您所见,两个测试的设置相同。结果如下:

使用inefficientRead()-方法

Starting benchmark
#1 Took 658ms
#2 Took 590ms
#3 Took 569ms
#4 Took 567ms
#5 Took 562ms
#6 Took 570ms
#7 Took 563ms
#8 Took 568ms
#9 Took 560ms
#10 Took 568ms

使用betterRead()-方法

Starting benchmark
#1 Took 42ms
#2 Took 10ms
#3 Took 5ms
#4 Took 7ms
#5 Took 16ms
#6 Took 3ms
#7 Took 4ms
#8 Took 5ms
#9 Took 5ms
#10 Took 13ms

我正在运行java-command 没有额外参数的测试。我正在运行MacMini3,1 from early 2009 和 Sun JDK 7:

[luke@BlackBox ~]$ java -version
java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) Client VM (build 23.5-b02, mixed mode)

这让我觉得差别很大。我在衡量这个时做错了什么,还是应该发生这种情况?

【问题讨论】:

  • 嗯,10 次迭代不足以作为基准测试,但除此之外它似乎 很好。尝试将迭代次数增加到 100,000 次,并添加一个“热身”阶段,在该阶段调用该方法 10,000 次(不计时),让 JIT 进行一些内联​​。
  • @CameronSkinner 显然您没有考虑到每个高级迭代已经包含 1661 次方法调用。
  • @MarkoTopolnik:基准测试正在通过betterReadinefficientRead 方法进行测量,因此您应该多次调用这些方法。所以你是对的:你没有考虑被测方法的实现细节。
  • @CameronSkinner 至少调用这些方法 10,000 次只是为了热身的建议显然是错误的:JVM JIT 根据通过任何单个的次数代码行,这里发生在第十次外部迭代之前。从那开始,您要求执行 100,000 次完整的文件加载过程是非常不合理的。即使是最严格的统计学家,最大 100 的样本量也能满足。
  • @MarkoTopolnik:我们可以同意不同意。但我建议您详细了解 JIT 在内联整个方法调用等方面可以做什么。

标签: java performance-testing string-concatenation


【解决方案1】:

我在衡量这个时做错了什么,还是应该发生这种情况?

这是应该发生的。使用重复的字符串连接构造一个长字符串是一种已知的性能反模式:每个 连接必须创建一个新字符串,其中包含原始字符串的 副本 和一个 copy 附加字符串。你最终会得到 O(N2) 的性能。当您使用StringBuilder 时,大多数时候您只是 将附加字符串复制到缓冲区中。有时缓冲区需要用完空间并需要扩展(通过将现有数据复制到新缓冲区中),但这种情况并不经常发生(由于缓冲区扩展策略)。

请参阅我的article on string concatenation 了解详细信息 - 这是一篇非常古老的文章,所以早于StringBuilder,但基本面没有改变。 (基本上StringBuilder 类似于StringBuffer,但没有同步。)

【讨论】:

  • 很好,现在我可以在哪里领取我的“得到 Jon Skeet 的回答”T 恤?
  • 乔恩说的很棒。回溯一步,如果不熟悉,String 是finalimmutable
【解决方案2】:

这正是应该发生的事情。 betterRead 需要线性时间; inefficientRead 需要二次方时间。

【讨论】:

  • @MarkoTopolnik:您误解了大 O 复杂度的含义。它衡量的是成本的增长率,而不是绝对成本。如果您将Thread.sleep(1000) 添加到betterRead 方法中,它仍然是O(n),但在挂钟时间上比O(n^2) 方法花费的时间要长得多,至少在n 变大之前是这样。
  • @MarkoTopolnik:我当然会暗示常数因素无关紧要。
  • 从第一次关心渐近线,到后来优化常数因子。
  • 没有不同的算法最终得到完全相同的常数因子,我很乐意在这里省略它们为何不同的讨论。
猜你喜欢
  • 2011-01-31
  • 2011-11-12
  • 2012-02-13
  • 1970-01-01
  • 2010-11-28
  • 1970-01-01
  • 2011-06-28
  • 1970-01-01
  • 2011-12-26
相关资源
最近更新 更多