C#加速字符串?

时间:2011-02-05 11:22:20

标签: c# string performance

struct mydata
{
    public int id;
    public string data;
}

class Program
{
    static void Main(string[] args)
    {
        List<mydata> myc = new List<mydata>();

        Stopwatch stopwatch = new Stopwatch();

        stopwatch.Start();

        for (int i = 0; i < 1000000; i++)
        {
            mydata d = new mydata();

            d.id = i;
            d.data = string.Format("DataValue {0}",i);

            myc.Add(d);
        }

        stopwatch.Stop();
        Console.WriteLine("End: {0}", stopwatch.ElapsedMilliseconds);
}

这个代码上面的代码是如此慢......? 在较旧的笔记本电脑上,时间是: 上面的C#代码:1500ms Delphi中的类似代码:450ms ....

然后我将代码更改为KeyValue / Pair(见下文):

Stopwatch stopwatch = new Stopwatch();

        stopwatch.Start();

        var list = new List<KeyValuePair<int , string>>();

        for (int i = 0; i < 1000000; i++)
        {
            list.Add(new KeyValuePair<int,string>(i, "DataValue" + i));
        }

        stopwatch.Stop();
        Console.WriteLine("End: {0}", stopwatch.ElapsedMilliseconds);
        Console.ReadLine();

这改善了1150毫秒的时间..

如果我删除'+ i',则时间是&lt; 300ms的

如果我尝试用StringBuilder替换它,时间是相似的。

        stopwatch.Start();

        var list = new List<KeyValuePair<int , string>>();

        for (int i = 0; i < 1000000; i++)
        {
            list.Add(new KeyValuePair<int,string>(i, "DataValue" + i));
        }

        stopwatch.Stop();
        Console.WriteLine("End: {0}", stopwatch.ElapsedMilliseconds);
        Console.ReadLine();

略好一点..如果你删除sb.Append(i)它的速度很快..

看起来任何时候你必须将一个Int添加到一个字符串/ stringbuilder它非常慢......

我能以任何方式加快速度吗?

编辑**

以下代码是我提出建议后最快的代码:

        StringBuilder sb = new StringBuilder();
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();

        var list = new List<KeyValuePair<int, string>>();

        for (int i = 0; i < 1000000; i++)
        {
            sb.Append("DataValue");
            sb.Append(i);
            list.Add(new KeyValuePair<int, string>(i, sb.ToString()));
            sb.Clear();
        }

        stopWatch.Stop();
        Console.WriteLine("End: {0}", stopWatch.ElapsedMilliseconds);
        Console.ReadLine();

如果我更换线路:

        StringBuilder sb = new StringBuilder();
        Stopwatch stopWatch = new Stopwatch();
        stopWatch.Start();

        var list = new List<KeyValuePair<int, string>>();

        for (int i = 0; i < 1000000; i++)
        {
            sb.Append("DataValue");
            sb.Append(i);
            list.Add(new KeyValuePair<int, string>(i, sb.ToString()));
            sb.Clear();
        }

        stopWatch.Stop();
        Console.WriteLine("End: {0}", stopWatch.ElapsedMilliseconds);
        Console.ReadLine();
有:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics; using System.Threading;

namespace ConsoleApplication1 { struct mydata { public int id; public string data; }

class Program
{
    static void Main(string[] args)
    {
        List<mydata> myc = new List<mydata>();

        Stopwatch stopwatch = new Stopwatch();

        stopwatch.Start();

        for (int i = 0; i < 1000000; i++)
        {
           mydata d = new mydata();
           d.id = i;
           d.data = "DataValue " + i.ToString();
           myc.Add(d);
        }

        stopwatch.Stop();
        Console.WriteLine("End: {0}", stopwatch.ElapsedMilliseconds);
        Console.ReadLine();
    }

}

}

在我的家用机器上,这是从660毫秒 - > 31毫秒..

是的..用'+ i.ToString()'

慢了630ms

但仍然比拳击/ string.format等快2倍..

class Program
{
    static void Main(string[] args)
    {
        List<mydata> myc = new List<mydata>();

        Stopwatch stopwatch = new Stopwatch();

        stopwatch.Start();

        for (int i = 0; i < 1000000; i++)
        {
           mydata d = new mydata();
           d.id = i;
           d.data = "DataValue " + i.ToString();
           myc.Add(d);
        }

        stopwatch.Stop();
        Console.WriteLine("End: {0}", stopwatch.ElapsedMilliseconds);
        Console.ReadLine();
    }

}

是612ms ..(如果List&gt;(1000000);是预先初始化的,速度没有差异。

4 个答案:

答案 0 :(得分:10)

前两个示例的问题是必须先将整数装箱,然后再转换为字符串。拳击导致代码变慢。

例如,在这一行:

d.data = string.Format("DataValue {0}", i);

string.Format的第二个参数是object,这导致i的装箱。请参阅中间语言代码以确认:

...
box int32
call string [mscorlib]System.String::Format(string, object)
...

同样这段代码:

d.data = "DataValue " + i;

相当于:

d.data = String.Concat("DataValue ", i);

这使用String.Concat的重载,参数类型为object,所以这又涉及装箱操作。这可以在生成的中间语言代码中看到:

...
box int32
call string [mscorlib]System.String::Concat(object, object)
...

为了获得更好的性能,这种方法避免了拳击:

d.data = "DataValue " + i.ToString();

现在,中间语言代码不包含box指令,它使用带有两个字符串的String.Concat重载:

...
call instance string [mscorlib]System.Int32::ToString()
call string [mscorlib]System.String::Concat(string, string)
...

答案 1 :(得分:1)

在我的机器上:

... String.Format("DataValue {0}", i ) // ~1650ms
... String.Format("DataValue {0}", "") // ~1250ms
... new MyData {Id = i, Data = "DataValue {0}" + i} // ~1200ms

正如马克所说,这是一场拳击行动。

对于这种特定情况,当您根据您的id获取DataValue时,您可以创建一个get属性或覆盖ToString()方法,以便在您需要时执行该操作。

public override string ToString()
{
    return "DataValue {0}" + Id;
}

答案 2 :(得分:0)

上面有很多问题会影响你的结果。 首先,你所做的比较都不相同。在你有一个列表,并使用添加,你添加到列表中的内容不会影响时间,将List的声明更改为var不会影响时间。

我不相信Mark提出的拳击论证,这可能是一个问题,但我很确定在第一种情况下会对.ToString进行隐式调用。这有自己的开销,即使int被装箱也需要。

格式是一项非常昂贵的操作。 第二个版本有一个字符串连接,可能比.Format便宜。

第三种方式一直很昂贵。使用这样的字符串生成器效率不高。在内部,stringbuilder只是一个列表。当你在它上面执行.ToString时,你基本上会做一个大的concat操作。

如果你拿出一个关键线,某些操作可能会突然快速运行的原因是编译可以优化代码位。如果它似乎一遍又一遍地做同样的事情,它可能不会这样做(粗略过度简化)。

是的,所以这是我的建议:

第一个版本可能是我心目中最接近“正确”的版本。你能做的是推迟一些处理。获取对象mydata并设置字符串属性AND int属性。然后只有当你需要读取字符串时才通过concat产生输出。如果您要重复打印操作,请保存。它不会像你期望的那样更快。

答案 3 :(得分:0)

此代码中的另一个主要性能杀手是List。在内部,它将项目存储在数组中。当您调用Add时,它会检查新Item是否适合数组(EnsureCapacitiy)。当它需要更多空间时,它将创建一个大小为double的新数组,然后将旧数组中的项目复制到新数组中。如果您在Reflector中查看List.Add,您可以看到所有这些。

因此,在包含1,000,000个项目的代码中,它需要将数组复制大约25次,并且每次都会更大。

如果您将代码更改为

var list = new List<KeyValuePair<int, string>>(1000000);

你应该看到速度的急剧增加。让我们知道您的情况!

此致

GJ