Когда и зачем мне использовать 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, где производительность обработки строк не является проблемой.
Ответы
Ответ 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 не имеет этой проблемы, поэтому на самом деле не требуется построитель строк в качестве обходного пути.