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);是预先初始化的,速度没有差异。
答案 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