foreach中的Stringbuilder比for和String.Join()SUCKS更慢?

时间:2011-05-01 22:20:09

标签: c# arrays performance string

在SO上看到一个问题,关于加入字符串我已经做了一些测试,并且知道在foreach中加入字符串比使用for循环并使用数组中的索引要慢。由于绑定检查数组,for循环不应该更慢吗? (对foreach上不存在的字符串[i]的绑定检查)。

我不明白的另一件事是列表中的string.Join()慢度......

编辑:将错误和更新的源更新为最终源(删除最后一个“,”)

以下是测试结果:

DEBUG:
   AMD PHENOM II X4 3GHZ
    StringBuilder foreach System.Action Time: 4077ms (12025926)
    StringBuilder for System.Action Time: 4032ms (11895082)
    String.Join System.Action Time: 5338ms (15744918)
   INTEL XEON W3503 @ 2.4GHZ / 12GB DDR3
    StringBuilder foreach System.Action Time: 4661ms (10926950)
    StringBuilder for System.Action Time: 4202ms (9849590)
    String.Join System.Action Time: 6466ms (15156149)

RELEASE:
   AMD PHENOM II X4 3GHZ
    StringBuilder foreach System.Action Time: 3897ms (11496978)
    StringBuilder for System.Action Time: 3719ms (10970899)
    String.Join System.Action Time: 5307ms (15655162)
   INTEL XEON W3503 @ 2.4GHZ / 12GB DDR3
    StringBuilder foreach System.Action Time: 4533ms (10625128)
    StringBuilder for System.Action Time: 4168ms (9770765)
    String.Join System.Action Time: 7173ms (16813036)
    (why in the world xeon slower than in debug with string.join?)

FOR A GOOD LAUGH LOOK AT THE END.

以下是来源:

public static void Main(string[] Args)
{
    List<string> strings = new List<string>() {};
    for (double d = 0; d < 12000; d++) {
        strings.Add(d.ToString());
    }

    GC.Collect();
    GC.WaitForPendingFinalizers();

    Performance(() =>
    {
            StringBuilder sb = new StringBuilder();
            foreach (string s in strings)
            {
                sb.Append(s);
                sb.Append(",");
            }
            sb.Remove(sb.Length - 1, 1);
    }, "StringBuilder foreach");

    GC.Collect();
    GC.WaitForPendingFinalizers();

    Performance(() =>
    {
        StringBuilder sb = new StringBuilder();
        int max = strings.Count-1;
        int i;
        for (i = 0; i < max; i++)
        {
            sb.Append(strings[i]);
            sb.Append(",");
        }
        sb.Append(strings[i]);
    }, "StringBuilder for");

    GC.Collect();
    GC.WaitForPendingFinalizers();

    Performance(() =>
    {
        string s = string.Join(",", strings);
    }, "String.Join");


}
public static void Performance(Action fn, string prefix)
{
    var timer = new Stopwatch();
    timer.Start();

    for (var i = 0; i < 10000; ++i)
    {
        fn();
    }

    timer.Stop();

    Console.WriteLine("{0} {1} Time: {2}ms ({3})", prefix, fn.ToString(), timer.ElapsedMilliseconds, timer.ElapsedTicks);
}

字符串是否像foreach中的值类型一样被复制?由于速度几乎相同......

编辑:

澄清为什么int max = strings.Count-1;可能是与人们所说的相反的优化(并且测试证明是这样):

我们不是在处理数组,而且集合来自外部作用域到迭代它的方法。如果它是在for循环中的strings.Length,那可能会改变(就像另一个线程更改集合)..但这不是原因,原因是我们正在读取变量而不是调用方法(属性获取)和它只提供5%的性能。这不是绑定检查的编译时优化,因为没有人能提前知道“最大”值。它取决于每次调用方法时字符串的内容。

EDIT2:

在发布时测试中有一个更大的字符串,但数量相同,请大笑String.Join():

List<string> strings = new List<string>() {};
for (double d = 0; d < 12000; d++) {
    strings.Add("ikugluglizuglkuhiugpiugiugholiugholiughpiuhziuhzuiugloiu" + d.ToString());
}

// AMD PHENOM:
//     StringBuilder foreach System.Action Time: 10080ms (29732687)
//     StringBuilder for System.Action Time: 9659ms (28490593)
//     String.Join System.Action Time: 24509ms (72292291)
// INTEL XEON:
//     StringBuilder foreach System.Action Time: 9790ms (22947294)
//     StringBuilder for System.Action Time: 9140ms (21425490)
//     String.Join System.Action Time: 21114ms (49490839)

它可能适用于数组,但是在集合中String.Join粗暴地吮吸,对于大字符串更是如此!

如果您想比较,仅供参考:

Windows 7 64bit
CPU Type    QuadCore AMD Phenom II X4 945
CPU Clock   3000 MHz
L3 Cache    6 MB  (On-Die, ECC, NB-Speed)
North Bridge Clock  2010.8 MHz
Memory 8190 MB
Memory Bus  804.3 MHz DDR3-1600
Motherboard Chipset AMD 790X, AMD K10
Memory Timings  8-9-9-24  (CL-RCD-RP-RAS)
Command Rate (CR)   1T

2 个答案:

答案 0 :(得分:2)

  

由于对数组的绑定检查,for循环不应该更慢吗?

不,CLR可以将其优化为1以检查它是否可以验证边界。

 int max = strings.Count - 1;

糟糕的优化。在FX 1.1中,它会花费你。 (这也是不正确的。)

foreach必须做更多的工作(通过Eumerator)。注意差异很小。

答案 1 :(得分:1)

查看ILSpy中的IL代码。

foreach循环有一个隐式的尝试..最后,for循环没有。

在foreach中也有对MoveNext的隐式调用。 MoveNext有很多开销,包括在最终对列表进行索引操作以获取条目之前的版本更改检查。

理论上foreach可以更快,但它显然比裸循环做更多的工作。

这是在vs2010之下。不确定其他版本如何处理这个问题。