【问题标题】:Performance issues with nested loops and string concatenations嵌套循环和字符串连接的性能问题
【发布时间】:2014-02-02 07:52:04
【问题描述】:

谁能解释一下为什么这段代码需要这么长时间才能运行(即 >24 小时): 行数为 5000,列数为 2000(即大约 10m 循环)。

有更好的方法吗????

for (int i = 0; i < m.rows; i++)
{
    for (int j = 0; j < m.cols; j++)
    {
        textToWrite += m[i, j].ToString() + ",";
    }
    //remove the final comma.
    textToWrite = textToWrite.Substring(0,textToWrite.Length-2);
    textToWrite += Environment.NewLine;
}

【问题讨论】:

  • 很可能是因为您使用每个 textToWrite += whatever 创建了巨大的字符串。在 Java 中可以使用 StringBuilder 有效地创建大字符串,尝试在 C# 中找到等效项。
  • 谢谢大家!当然,这似乎起到了作用。我会将答案标记为我用来帮助我解决问题的代码。我以前使用过 StringBuilders,但没有很长时间,我也不知道为什么。现在我愿意!!!

标签: c# .net string performance


【解决方案1】:

是的,+= 运算符效率不高。请改用StringBuilder

在 .NET 框架中,字符串是不可变的,这意味着它不能就地修改。这意味着+= 运算符每次都必须创建一个新字符串,这意味着分配内存、复制现有字符串的值并将其写入新位置。一个或两个连接是可以的,但是一旦你把它放在一个循环中,你就需要使用一个替代方案。

http://support.microsoft.com/kb/306822

使用以下代码,您将看到性能大幅提升:

var textToWriteBuilder = new StringBuilder();

for (int i = 0; i < m.rows; i++)
{
    for (int j = 0; j < m.cols; j++)
    {
        textToWriteBuilder.Append(m[i, j].ToString() + ",");
    }

    // I've modified the logic on the following line, I assume you want to 
    // concatenate the value instead of overwriting it as you do in your question.
    textToWriteBuilder.Append(textToWriteBuilder.Substring(0, textToWriteBuilder.Length - 2));
    textToWriteBuilder.Append(Environment.NewLine);
}

string textToWrite = textToWriteBuilder.ToString();

【讨论】:

  • 请解释为什么它效率不高,如果他可以计算缓冲区的最终大小,他如何分配缓冲区?
  • 如果连接效率较低,C# 编译器将始终使用 StringBuilder。 有时 连接效率较低。常数因子开销不是问题。问题是二次运行时间。
  • @BlueTrin 类字符串是不可变的,因此每次在内存中创建新对象。这就是使用 stringbuilder 的原因
  • @NitinVarpe:我知道答案,我希望他编辑他的答案。
【解决方案2】:

假设textToWriteString,您应该改用StringBuilderString 是不可变的,添加小部件非常无效。

理想情况下,您应该以合理的大小初始化StringBuilder(请参阅doc)。

【讨论】:

    【解决方案3】:

    使用StringBuilder 而不是几百万个串联。

    如果你连接两个字符串,这意味着系统会分配新的内存来包含它们,然后将它们都复制进去。无数的大内存分配和复制操作变得非常缓慢。

    StringBuilder 所做的是通过“提前”分配来极大地减少这种情况,因此只需将缓冲区增长几次然后将其复制进去,从而消除循环中迄今为止最慢的因素。

    【讨论】:

      【解决方案4】:

      假设矩阵大小为 MxM 并且有 N 个元素。您正在以迭代次数采用 O(N^2)(或 O(M^4))的方式构建字符串。每个操作都必须复制已经存在的内容。问题是不是一些像临时字符串这样的常数因子开销。

      使用 StringBuilder。

      字符串连接对于少量连接的字符串更有效。对于动态数量的字符串,请使用 StringBuilder。

      【讨论】:

      • 有没有办法避免枚举所有矩阵元素的算法是O(n^2)
      • 在我的示例中,N 是连接数。如果将 M 设为矩阵维度,则此算法为 O(M^4)。
      • 谢谢。我认为 n 是行数/列数。
      【解决方案5】:

      我看到的最大问题是您使用 textToWrite 作为string

      由于字符串是不可变的,因此每次更改字符串时都必须保留从先前版本复制的新内存。

      更有效的方法是使用专为此类场景设计的StringBuilder 类。例如:

      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < m.rows; i++)
      {
          for (int j = 0; j < m.cols; j++)
          {
              sb.Append(m[i, j].ToString());
              if(j < m.cols - 1) // don't add a comma on the last element
              {
                sb.Append(",");
              }
          }
          sb.AppendLine();
      }
      

      【讨论】:

      • 您的代码实际上更好 - 您使用了正确实现删除最后一个逗号
      【解决方案6】:

      您的代码需要很长时间,因为您要附加字符串,并在执行过程中创建数以千计的新临时字符串。内存管理器需要为这些字符串找到内存(内存需求会随着它们变长而增加),并且该操作会将您目前拥有的字符(每次迭代的数量增加)复制到最新的字符串。

      另一种方法是使用单个StringBuilder,在其上调用Append() 以更有效地追加,最后在完成获得要使用的最终字符串时调用ToString()

      【讨论】:

      • 临时人员不是问题。问题是二次运行时间。
      【解决方案7】:

      因为你要创建大量的字符串。

      您应该为此使用 StringBuilder。

      StringBuilder sb = new StringBuildeR();
      
      for (int i = 0; i < m.rows; i++)
      {
          bool first = true;
      
          for (int j = 0; j < m.cols; j++)
          {
              sb.Append(m[i, j]);
      
              if (first)
              {
                  first = false;
              }
              else
              {
                  sb.Append(",");
              }
          }
      
          sb.AppendLine();
      }
      
      string output = sb.ToString();
      

      【讨论】:

        【解决方案8】:

        运行需要这么长时间的原因是因为您正在使用字符串连接来创建字符串。对于每次迭代,它会将整个字符串复制到一个新字符串中,因此最终您将复制的字符串加起来是最终字符串的数百万倍。

        使用StringBuilder 创建字符串:

        StringBuilder textToWrite = new StringBuilder();
        for (int i = 0; i < m.rows; i++)
        {
            for (int j = 0; j < m.cols; j++)
            {
                if (j > 0) textToWrite.Append(',');
                textToWrite.Append(m[i, j]);
            }
            textToWrite.AppendLine();
        }
        

        【讨论】:

          猜你喜欢
          • 2014-05-18
          • 1970-01-01
          • 1970-01-01
          • 2014-12-09
          • 1970-01-01
          • 2020-11-30
          • 2016-02-08
          • 2021-01-10
          • 1970-01-01
          相关资源
          最近更新 更多