在连接字符串时,C#中的循环怎么这么慢?

时间:2018-04-27 22:35:40

标签: c# c++ performance for-loop

我编写了一个在C ++和C#中运行简单for循环的程序,但同样的事情在C#中需要花费更长的时间,为什么呢?我没有在考试中考虑到某些问题吗?

C#(13.95s)

static double timeStamp() {
    return (double)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
}

static void Main(string[] args) {
    double timeStart = timeStamp();

    string f = "";
    for(int i=0; i<100000; i++) {
        f += "Sample";
    }

    double timeEnd = timeStamp();
    double timeDelta = timeEnd - timeStart;
    Console.WriteLine(timeDelta.ToString());
    Console.Read();
}

C ++(0.20s)

long int timeStampMS() {
    milliseconds ms = duration_cast<milliseconds> (system_clock::now().time_since_epoch());
    return ms.count();
}

int main() {
    long int timeBegin = timeStampMS();

    string test = "";

    for (int i = 0; i < 100000; i++) {
        test += "Sample";
    }


    long int timeEnd = timeStampMS();
    long double delta = timeEnd - timeBegin;

    cout << to_string(delta) << endl;
    cin.get();
}

3 个答案:

答案 0 :(得分:4)

在我的电脑上,将代码更改为使用StringBuilder并在结尾处转换为String,执行时间从26.15秒增加到0.0012秒,或者快了20,000倍。

var fb = new StringBuilder();
for (int i = 0; i < 100000; ++i) {
    fb.Append("Sample");
}
var f = fb.ToString();

正如.Net文档中所解释的,StringBuilder类是一个可变的字符串对象,对于对字符串进行许多更改时非常有用,而不是String类。一个不可变对象,每次你需要创建一个新对象将两个String连接在一起。由于StringBuilder的实现是字符数组的链接列表,并且新块已添加到8000 characters at a time,因此StringBuilder.Append要快得多。

答案 1 :(得分:4)

由于Strings是不可变的,因此每个连接都会创建一个新字符串 使用过的字符串已经死了,等待垃圾回收。

StringBuider实例化一次,并在需要时添加新的数据块,将其容量扩展到MakeRoom(.NET源代码)。

使用StringBuilder

进行测试
string StringToAppend = "Sample";
int IteratorMaxValue = 100000;

StringBuilder SB = new StringBuilder(StringToAppend.Length * IteratorMaxValue);

Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();

for (int i = 0; i < IteratorMaxValue; i++)
{
    SB.Append(StringToAppend);
}
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);

我的机器上有4毫秒。

答案 2 :(得分:4)

C ++循环可能很快,因为它实际上不需要做任何事情。一个好的优化器将能够证明删除整个循环在程序的行为中没有任何可观察到的差异(执行时间不算作可观察的)。我不知道是否允许C#运行时进行类似的优化。在任何情况下,为了保证合理的测量,您必须始终以可观察的方式使用结果。

假设优化器没有删除循环,将一个常量长度字符串附加到std::string已经分摊了常量的复杂性。 C#中的字符串是不可变的,因此操作每次都会创建字符串的新副本,因此它具有线性复杂性。字符串变得越长,渐近复杂度的这种差异变得越显着。通过在C#中使用可变StringBuilder,可以实现相同的渐近复杂度。