【问题标题】:When and Why Should I Use TStringBuilder?何时以及为什么应该使用 TStringBuilder?
【发布时间】:2010-12-07 19:56:24
【问题描述】:

一年前,我将我的程序从 Delphi 4 转换为 Delphi 2009,主要是为了跳转到 Unicode,同时也是为了从 Delphi 多年来的改进中获益。

因此,我的代码当然是所有遗留代码。它使用的短字符串现在都方便地变成了长 Unicode 字符串,并且我已经将所有旧的 ANSI 函数更改为新的等效函数。

但在 Delphi 2009 中,他们引入了 TStringBuilder 类,大概是仿照 .NET 的 StringBuilder 类。

我的程序进行了大量的字符串处理和操作,可以一次将数百兆字节的大字符串加载到内存中以供使用。

我对Delphi对TStringBuilder的实现了解不多,但听说它的一些操作比使用默认的字符串操作要快。

我的问题是,是否值得我努力并转换我的标准字符串以使用 TStringBuilder 类。这样做我会得到什么,会失去什么?


感谢您的回答并让我得出结论,除非需要 .NET 兼容性,否则不要打扰。

在他的博客Delphi 2009 String Performance, Jolyon Smith states

但在我看来,TStringBuilder 似乎主要是作为 .NET 兼容性夹具存在的,而不是为 Win32 应用程序的开发人员提供任何真正的好处,希望或需要单一来源 Win32 的开发人员可能例外/ .NET 代码库,其中字符串处理性能不是问题。

【问题讨论】:

  • 只是好奇,当您从 D4 短字符串转换为 D2009 Unicode 字符串时,您是否注意到字符串性能发生了变化?
  • 我没有直接对字符串进行计时,但我的升级导致代码性能提高了 25% - 可能是由于 FastMM 和新版本中内置的其他优化。必须将外部 ANSI 文件编码为 Unicode,这会占用双倍的空间,并且对于非常大的文件,这会增加程序的主要开销,从而逆转小文件的性能改进。阻塞到非常大的缓冲区减少了负担。总体而言,我觉得我的程序可能和以前一样快,但得益于 Unicode。
  • 谢谢,这很有趣。
  • @lkessler:很遗憾,您仍然重复有关“必须将外部 ANSI 文件编码为 Unicode”的错误信息。如果您将它们切换为 UTF-8(这是一种有效的 Unicode 编码),您的文件大小不会翻倍,并且您将丢失 nothing。相反,除非在快速 SSD 上,I/O 的减少可能比字符串重新编码的 CPU 周期的增加更重要,从而给您带来不错的性能提升。
  • Lachlan:另请参阅 Jan Goyvaert 的文章:“使用本机 Win32 字符串类型的速度优势”micro-isv.asia/2008/09/…

标签: delphi unicode delphi-2009 stringbuilder


【解决方案1】:

据我所知,TStringBuilder 的引入只是为了与 .NET 和 Java 进行某种程度的比较,它似乎更像是一种勾选框类型的功能,而不是任何主要的进步。

共识似乎是 TStringBuilder 在某些操作中更快,但在其他操作中更慢。

您的程序听起来很有趣,可以与 TStringBuilder 进行前后比较,但除了作为学术练习之外我不会这样做。

【讨论】:

    【解决方案2】:

    基本上,我使用这些习语来构建字符串。最重要的区别是:

    对于复杂的构建模式,第一个使我的代码更清晰,第二个仅当我添加行并且通常包含许多 Format 调用时。

    当格​​式模式很重要时,第三个使我的代码更清晰。

    我只在表达式很简单的时候使用最后一个。

    前两个成语之间的更多区别:

    • TStringBuilder 有很多 Append 的重载,还有 AppendLine(只有两个重载)如果你想添加像 TStringList.Add 这样的行可以
    • TStringBuilder 使用超容量方案重新分配底层缓冲区,这意味着在大缓冲区和频繁追加的情况下,它可以比 TStringList 快很多
    • 要获取TStringBuilder 内容,您必须调用ToString 方法,这会减慢速度。

    所以:速度不是选择字符串附加习语最重要的问题。可读的代码是。

    【讨论】:

      【解决方案3】:

      我尝试改进解析文本文件 (1.5GB) 的旧例程。该例程非常愚蠢,它正在构建这样的字符串:Result:= Result+ buff[i];

      所以,我认为 TStringBuilder 将显着提高速度。事实证明,“哑”代码实际上比使用 TStringBuilder 的“改进”版本快 114%。

      因此,从字符构建字符串并不是一个可以使用 TStringBuilder 提高速度的地方。


      我的 StringBuilder(下图)比经典的 s:= s+ chr 快 184.82 倍(是的 184 !!!!!!)。 (4MB 字符串的实验)

      经典 s:= s + c
      时间:8502 毫秒

      procedure TfrmTester.btnClassicClick(Sender: TObject);
      VAR
         s: string;
         FileBody: string;
         c: Cardinal;
         i: Integer;
      begin
       FileBody:= ReadFile(File4MB);
       c:= GetTickCount;
       for i:= 1 to Length(FileBody) DO
        s:= s+ FileBody[i];
       Log.Lines.Add('Time: '+ IntToStr(GetTickCount-c) + 'ms');     // 8502 ms
      end;
      

      预缓冲

      Time:  
           BuffSize= 10000;       // 10k  buffer = 406ms
           BuffSize= 100000;      // 100k buffer = 140ms
           BuffSize= 1000000;     // 1M   buffer = 46ms
      

      代码:

      procedure TfrmTester.btnBufferedClick(Sender: TObject);
      VAR
         s: string;
         FileBody: string;
         c: Cardinal;
         CurBuffLen, marker, i: Integer;
      begin
       FileBody:= ReadFile(File4MB);
       c:= GetTickCount;
      
       marker:= 1;
       CurBuffLen:= 0;
       for i:= 1 to Length(FileBody) DO
        begin
         if i > CurBuffLen then
          begin
           SetLength(s, CurBuffLen+ BuffSize);
           CurBuffLen:= Length(s)
          end;
         s[marker]:= FileBody[i];
         Inc(marker);
        end;
      
       SetLength(s, marker-1); { Cut down the prealocated buffer that we haven't used }  
       Log.Lines.Add('Time: '+ IntToStr(GetTickCount-c) + 'ms');
       if s <> FileBody
       then Log.Lines.Add('FAILED!');
      end;
      

      预缓冲,作为类

      Time:    
       BuffSize= 10000;       // 10k  buffer = 437ms       
       BuffSize= 100000;      // 100k buffer = 187ms        
       BuffSize= 1000000;     // 1M buffer = 78ms     
      

      代码:

      procedure TfrmTester.btnBuffClassClick(Sender: TObject);
      VAR
         StringBuff: TCStringBuff;
         s: string;
         FileBody: string;
         c: Cardinal;
         i: Integer;
      begin
       FileBody:= ReadFile(File4MB);
       c:= GetTickCount;
      
       StringBuff:= TCStringBuff.Create(BuffSize);
       TRY
         for i:= 1 to Length(FileBody) DO
          StringBuff.AddChar(filebody[i]);
         s:= StringBuff.GetResult;
       FINALLY
        FreeAndNil(StringBuff);
       END;
      
       Log.Lines.Add('Time: '+ IntToStr(GetTickCount-c) + 'ms');
       if s <> FileBody
       then Log.Lines.Add('FAILED!');
      end;
      

      这是类:

      { TCStringBuff }
      
      constructor TCStringBuff.Create(aBuffSize: Integer= 10000);
      begin
       BuffSize:= aBuffSize;
       marker:= 1;
       CurBuffLen:= 0;
       inp:= 1;
      end;
      
      function TCStringBuff.GetResult: string;
      begin
       SetLength(s, marker-1);                    { Cut down the prealocated buffer that we haven't used }
       Result:= s;
       s:= '';         { Free memory }
      end;
      
      procedure TCStringBuff.AddChar(Ch: Char);
      begin
       if inp > CurBuffLen then
        begin
         SetLength(s, CurBuffLen+ BuffSize);
         CurBuffLen:= Length(s)
        end;
      
       s[marker]:= Ch;
       Inc(marker);
       Inc(inp);
      end;
      

      结论: 如果您的字符串很大(超过 10K),请停止使用 s:= s + c。即使你有小字符串但你经常这样做也可能是真的(例如,你有一个函数正在对一个小字符串进行一些字符串处理,但你经常调用它)。

      _

      PS:你可能还想看这个:https://www.delphitools.info/2013/10/30/efficient-string-building-in-delphi/2/

      【讨论】:

      • 感谢您添加这个迟到的答案。这是您链接到的一篇很棒的文章。
      【解决方案4】:

      TStringBuilder 的引入仅仅是为了提供一种源代码兼容机制,以便应用程序在 DelphiDelphi.NET 中执行字符串处理。您在 Delphi 中牺牲了一些速度,以换取 Delphi.NET

      中的一些潜在显着优势

      .NET 中的 StringBuilder 概念解决了该平台上字符串实现的性能问题,Delphi(本机代码)平台的问题根本没有。

      如果您不编写需要为本地代码和 Delphi.NET 编译的代码,那么根本没有理由使用 TStringBuilder

      【讨论】:

      • 仅仅为了源代码兼容性而引入它是不正确的。那是它的一部分,但另一个重要的原因是它是一个强大的类,并且因为有些人更喜欢它能够执行流畅的编码模式。底线——想用就用,不想用就别用。
      • 真正好奇:“厉害”如何?为什么?至于流畅?是的,正如你所说,如果你想使用它,如果你不想要就不要使用它(或者如果你想在调试 时保持理智)。
      • @Deltics - 引入 TStringBuilder 可能只是为了与 .NET 兼容,但我猜这大概是 95% 的原因;-) TStringBuilder 的可能性有多大如果 Delphi.NET 需求从未存在过,是否会引入?
      • @IanH Mike Lischke 的 VirtualTrees 比 Embarcadero 早几年推出了 TStringBuilder。它是使用 unicode 字符串创建的加速操作。唯一的支持来自 Window 的 BSTR,Delphi 将其公开为WideString。 Windows 无法 realloc 一个 BSTR(BSTR 不被引用计数;SysReallocString 创建第二个字符串并进行复制)。在这种情况下,德尔福TStringBuilder 是必需品。之后,如果要添加很多东西,在字符串上设置Capacity 的能力会很有用。另外,这是一个很好的语法。
      【解决方案5】:

      根据Marco Cantu 不是为了速度,但您可能会获得更简洁的代码和更好的代码与 .Net 的兼容性。 Here(以及一些更正here)另一个速度测试,TStringBuilder 没有更快。

      【讨论】:

      • 感谢您的链接。他们帮助了。但我不同意您的观点,即 StringBuilder 提供了更清晰的代码。我绝对喜欢:s := s + s2;优于:SB.Append(s2);
      • 来自 Marco Cantu 的文章,我相信他的意思是在添加各种数据类型时。当然,差别不大。
      • 这是 Cantu 的字符串 concat 测试:“for i := 1 to 15 do s := s + 'xxx';”这不是什么大考验。 for 循环应该更大。我敢打赌 TStringBuilder 在这种情况下会赢。不幸的是,我无法测试它,因为我没有 D2009。一般来说,这种模式在大多数语言中都很慢,因为必须不断地重新分配和复制字符串。
      • 但我可能是错的。我只是用大的 for 循环做了一个快速测试,它并没有像我预期的那样爬行停止。我猜 Delphi 必须每次修改字符串并分配额外的空间。对于具有不可变字符串的语言,循环会越来越慢。
      【解决方案6】:

      TStringBuilder 基本上只是一个模仿功能,就像 LachlanG 所说的那样。它在 .NET 中是必需的,因为 CLR 字符串是不可变的,但 Delphi 没有这个问题,因此它实际上不需要字符串生成器作为解决方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-22
        • 2023-03-08
        • 2013-01-29
        • 1970-01-01
        • 2021-01-11
        • 2011-06-07
        • 1970-01-01
        相关资源
        最近更新 更多