Подтвердить что ты не робот

Когда и зачем мне использовать TStringBuilder?

Я преобразовал свою программу из Delphi 4 в Delphi 2009 год назад, главным образом, чтобы сделать переход на Unicode, но и получить преимущества всех этих улучшений Delphi за эти годы.

Мой код, конечно же, является всем устаревшим кодом. Он использует короткие строки, которые теперь все время стали длинными строками Unicode, и я изменил все старые функции ANSI на новый эквивалент.

Но с Delphi 2009 они представили класс TStringBuilder, предположительно смоделированный после класса StringBuilder.NET.

Моя программа выполняет много операций по обработке и обработке строк и может загружать сотни мегабайт больших строк в память сразу для работы.

Я не знаю много о реализации Delphi TStringBuilder, но я слышал, что некоторые из его операций быстрее, чем использование операций по умолчанию по умолчанию.

Мой вопрос в том, стоит ли мне обойти усилия и преобразовать стандартные строки для использования класса TStringBuilder. Что я получу и проиграю от этого?


Благодарим вас за ответы и приведу меня к моему заключению, что не должно беспокоить, если не требуется совместимость с .NET.

В своем блоге на Delphi 2009 String Performance, Джолион Смит утверждает:

Но мне кажется, что TStringBuilder существует в первую очередь как совместимость с .NET, а не для того, чтобы предоставить реальную выгоду разработчикам приложений Win32, за исключением, возможно, разработчиков, желающих или нуждающихся в одиночном источнике Win32/.NET codebase, где производительность обработки строк не является проблемой.

4b9b3361

Ответ 1

Насколько я знаю, TStringBuilder был введен только для некоторого контроля над .NET и Java, кажется, что это больше похоже на функцию типа ящика, чем на любой крупный шаг.

Консенсус, похоже, заключается в том, что TStringBuilder быстрее работает в некоторых операциях, но медленнее в других.

Ваша программа звучит как интересная, чтобы сделать сравнение до/после TStringBuilder, но я бы не стал делать это иначе, как в академическом упражнении.

Ответ 2

В принципе, я использую эти идиомы для построения строк. Наиболее важными отличиями являются:

Для сложных шаблонов сборки первый делает мой код намного чище, второй - только если я добавляю строки и часто включает в себя многие вызовы Format.

Третий делает мой код более чистым, когда шаблоны формата важны.

Я использую последний, только когда выражение очень просто.

Еще несколько отличий между двумя идиомами:

  • TStringBuilder имеет много перегрузок для Append, а также AppendLine (только с двумя перегрузками), если вы хотите добавить строки, такие как TStringList.Add могут
  • TStringBuilder перераспределяет базовый буфер с помощью схемы с более высокой пропускной способностью, что означает, что с большими буферами и частыми добавлениями он может быть намного быстрее, чем TStringList
  • Чтобы получить контент TStringBuilder, вы должны вызвать метод ToString, который может замедлить работу.

Итак: скорость не самая важная вещь, чтобы выбрать строку, добавляющую идиому. Читаемый код.

Ответ 3

Я попытался улучшить старую процедуру, которая разбирала текстовый файл (1,5 ГБ). Процедура была довольно глупой, и она строила такую строку: Result:= Result+ buff[i];

Поэтому я подумал, что TStringBuilder добавит значительные улучшения скорости. Оказалось, что "тупой" код был на 114% быстрее, чем "улучшенная" версия с TStringBuilder.

Таким образом, построение строки из символов НЕ является тем местом, где вы можете добиться улучшения скорости с помощью TStringBuilder.


Мой StringBuilder (ниже) в 184,82 раза (да 184 !!!!!!) быстрее классического s: = s+ chr. (Эксперимент на строке 4 МБ)

Классический 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;

Prebuffered

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;

Prebuffered, как класс

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;

Вывод: прекратите использовать s: = s + c, если у вас большие (более 10К) строки. Это может быть правдой, даже если у вас есть небольшие строки, но вы делаете это часто (например, у вас есть функция, которая выполняет некоторую обработку строки для небольшой строки, но вы часто ее вызываете). _

PS: Вы можете также хотеть видеть это: https://www.delphitools.info/2013/10/30/efficient-string-building-in-delphi/2/

Ответ 4

TStringBuilder был представлен исключительно для обеспечения совместимого с исходным кодом механизма для приложений для обработки строк в Delphi и Delphi.NET. Вы жертвуете некоторой скоростью в Delphi для некоторых потенциально значимых преимуществ в Delphi.NET

Концепция StringBuilder в .NET устраняет проблемы с производительностью с реализацией строки на этой платформе, возникает проблема, когда платформа Delphi (собственный код) просто не имеет.

Если вы не пишете код, который нужно скомпилировать как для собственного кода, так и для Delphi.NET, тогда просто нет причин использовать TStringBuilder.

Ответ 5

Согласно Marco Cantu не для скорости, но вы можете получить более чистый код и лучшую совместимость кода с .Net. Здесь (и некоторые исправления здесь) еще один тест скорости с TStringBuilder не быстрее.

Ответ 6

TStringBuilder - это, по сути, только функция me-too, как сказал LachlanG. Это необходимо в .NET, потому что строки CLR неизменяемы, но Delphi не имеет этой проблемы, поэтому на самом деле не требуется построитель строк в качестве обходного пути.