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

Год назад я перевел свою программу с Delphi 4 на Delphi 2009, в основном для перехода на Unicode, а также для получения преимуществ от всех этих лет улучшений Delphi.

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

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

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

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

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


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

В своем блоге о струнном исполнении Delphi 2009 Джолион Смит заявляет:

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

6 ответов

Решение

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

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

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

Я попытался улучшить старую процедуру, которая разбирала текстовый файл (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
Время:
BuffSize = 10000; // буфер 10k = 406ms BuffSize= 100000; // буфер 100k = 140ms BuffSize= 1000000; // 1М буфер = 46мс

Код:
процедура TfrmTester.btnBufferedClick(Отправитель: TObject); VAR s: строка; FileBody: строка; с: кардинал; CurBuffLen, marker, i: целое число; 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, как класс
Время:
BuffSize = 10000; // буфер 10 КБ = 437 мс
BuffSize = 100000; // буфер 100k = 187мс
BuffSize = 1000000; // 1М буфер = 78мс

Код:
процедура TfrmTester.btnBuffClassClick(Отправитель: TObject); VAR StringBuff: TCStringBuff; s: строка; FileBody: строка; с: кардинал; я: целое число; 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/

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам