Когда и почему я должен использовать 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/
По сути, я использую эти идиомы для построения строк. Наиболее важные отличия:
- Шаблон TStringBuilder.Create и Append, который добавляет новые символы в экземпляр TStringBuilder.
- Шаблон TStringList.Create и Add, который добавляет новые строки в текст экземпляра TStringList.
- Функция Format для сборки строк на основе шаблонов формата.
- Простая конкатенация строковых типов для выражений с 3 или менее значениями.
Для сложных шаблонов сборки, первый делает мой код намного чище, второй, только если я добавляю строки и часто включает в себя множество 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 такой проблемы нет, поэтому для обхода не требуется компоновщик строк.