使用char[]、StringBuilder和string在连接字符串时没有区别。

3
据说在拼接方面,char[] 的性能优于 StringBuilder,而 StringBuilder 的性能优于 string

在我的测试中,在循环内部使用 StringBuilderstring 没有明显的差异。实际上,char[] 是最慢的。

我正在针对具有 44 列和 130,000 行的相同表进行测试 查询是 select * from test

有人能帮我看看我是否做错了什么吗?

以下是代码:

//fetchByString(rd, fldCnt, delimiter, sw);            // duration: 3 seconds

//fetchByBuilder(rd, fldCnt, delimiter, sw, rsize);    // duration: 3 seconds

//fetchByCharArray(rd, fldCnt, delimiter, sw, rsize);  // duration: 7 seconds

private void fetchByString(OracleDataReader pReader, int pFldCnt, string pDelimiter, StreamWriter pWriter)
{
  while (pReader.Read())
  {
    string[] s = new string[pFldCnt];
    for (Int32 j = 0; j < pFldCnt; j++)
    {
      if (pReader.IsDBNull(j))
      {
        s[j] = "";
      }
      else
      {
        s[j] = pReader.GetValue(j).ToString();          // correct value
      }
    }
    pWriter.WriteLine(string.Join(pDelimiter, s));      
  }
}
private void fetchByBuilder(OracleDataReader pReader, int pFldCnt, string pDelimiter, StreamWriter pWriter, int pRowSzie)
{
  StringBuilder sb = new StringBuilder(pRowSzie);
  while (pReader.Read())
  {
    for (Int32 j = 0; j < pFldCnt; j++)
    {
      if (pReader.IsDBNull(j))
      {
        //sb.Append("");
        sb.Append(pDelimiter);
      }
      else
      {
        sb.Append(pReader.GetValue(j).ToString());          // correct value
        sb.Append(pDelimiter);
      }
    }
    pWriter.WriteLine(sb.ToString());
    sb.Clear();
  }
}
private void fetchByCharArray(OracleDataReader pReader, int pFldCnt, string pDelimiter, StreamWriter pWriter, int pRowSzie)
{
  char[] rowArray;
  int sofar; 
  while (pReader.Read())
  {
    rowArray = new char[pRowSzie];
    sofar = 0;
    for (Int32 j = 0; j < pFldCnt; j++)
    {
      if (pReader.IsDBNull(j))
      {
        pDelimiter.CopyTo(0, rowArray, sofar, pDelimiter.Length);
        sofar += pDelimiter.Length;
      }
      else
      {
        pReader.GetValue(j).ToString().CopyTo(0, rowArray, sofar, pReader.GetValue(j).ToString().Length);
        sofar += pReader.GetValue(j).ToString().Length;
        pDelimiter.CopyTo(0, rowArray, sofar, pDelimiter.Length);
        sofar += pDelimiter.Length;
      }
    }
    string a = new string(rowArray).TrimEnd('\0');
    pWriter.WriteLine(a);
  }
}

请发布您正在进行性能分析的代码。这是在编译器优化中吗?您处于调试还是发布模式下?这里提供的信息不足够详细。 - asawyer
1
OracleDataReader 方法花费了多少时间? - Miserable Variable
6
这些测试不会提供任何有用的信息。你正在尝试对“连接(concatenation)”进行性能测试时,同时还有输入/输出(IO)操作在进行,但它们并不会被记录在测试结果中。 - Perception
1
为什么要在字符串和数组之间来回转换,反复复制字符,并期望它更快呢?你可以选择 char 数组的速度或者字符串的便利性;如果想要同时拥有两者,你需要使用一个能够直接在 char 数组上进行字符串操作的库。 - cHao
2个回答

5

StringBuilder比字符串连接更受青睐,因为每个+运算符在字符串连接过程中经常需要分配临时的中间副本数据,这会快速消耗大量内存并需要多次复制数据。StringBuilder.Append()在内部进行了优化,避免了多次复制或分配子段。所有操作都发生在StringBuilder.ToString时,当输出字符串的最终大小已知并且可以一次性分配时。

您的测试用例没有使用字符串连接。您将一堆字符串片段分配到一个字符串数组中,然后调用String.Join。这基本上是StringBuilder在内部执行的操作。即使在删除可能占主导地位的数据I/O开销后,我也预计String.Join()和StringBuilder.ToString()将产生类似的性能。


谢谢回复。你的答案解释了为什么StringBuilder和string[]表现相同。我很感激。 - Shawn
我仍然在想为什么 char[] 比 StringBuilder 的性能表现还要差。我的计时如下: begin = DateTime.Now; fetchByString(rd, fldCnt, delimiter, sw); end = DateTime.Now; TimeSpan diff = end - begin; Console.WriteLine("duration = {0}",diff.Seconds.ToString()); - Shawn
计时包括对读取器的调用。在三种方法中,我以相同的方式访问了读取器并使用了相同数量的数据。在循环中,访问读取器而不进行 string/char[] 分配的时间约为 500 毫秒。cHao 的答案涉及 char[] 分配,但我不知道有没有直接从 OracleDataReader 中获取 char[] 的方法。有人知道吗?我计时整个方法是因为我想找到将数据放入输出变量的最快方法来提高性能。 - Shawn
1
好的,你在char[]例程中两次调用pReader.GetValue(j).ToString()。将其分配给一个临时变量,然后在多个位置使用该临时变量。如果pReader缓存数据,则调用两次的成本仅为所需的数据转换成为字符串所需的成本。如果pReader不缓存,则成本是它会使你的I/O开销翻倍。 - dthorpe
我对OracleDataReader一无所知。如果它在内部偏爱char[]作为数据格式,那么弄清楚如何访问可能会提高性能。否则,很有可能字符串是首选的内部格式。看看你能否找出数据库中数据的编码方式。如果它已经在数据库中是Unicode编码,那么你就不需要进行任何代码页转换成本。如果它是以代码页编码存储的,使客户端代码与该编码匹配可能会为您在ToString()中节省昂贵的转换步骤。 - dthorpe

2

我不太了解这个说法,但是你的char[]写法似乎进行了更多的转换。

pReader.GetValue().ToString()除了将值放在一个与你正在使用的格式不同的格式中(即string而不是char[]),在char[]赋值中发生了3次,而在其他地方只有1次。你应该找到一种方法来直接将你的“真实值”强制转换为char[]以使其有效。否则,从基准测试的角度来看,你可能会通过引入其他缓慢的因素来降低性能。我不是在断言这就是发生的事情,但从程序上考虑这很重要。即使你不能这样做,我认为如果你加入var stringRep = pReader.GetValue().ToString()并使用stringRep代替相关的GetValue/ToString调用,你仍然可能会获得小幅度的性能提升。

顺便说一句,我不确定你是如何计时的,但如果你没有使用Stopwatch类,你可以尝试使用它,以确保你的计时是适当的。它基本上是为这种基准测试而设计的。这也将允许你真正隔离你想要基准测试的内容(字符串连接操作),而不会混杂着来自Oracle读取器的所有混乱因素。


嗨,我按照您的建议减少了对读取器的调用,这将时间从7秒减少到3秒。 - Shawn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接