уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.

Работа со строками в Delphi — это отдельная и очень обширная тема. Рассмотреть в одной статье все тонкости и особенности строк delphi представляет собой достаточно сложную задачу сравнимую, наверное, с написанием небольшой книги, просто по причине того, что в Delphi на сегодняшний день используются разные типы строк (ShortString, AnsiString, UnicodeString и прочие) и каждый тип для чего-то да нужен — для обратной совместимости, для COM BSTR, для разработки под мобильные платформы и так далее.

Вместе с этим, Delphi постоянно развивается, что-то добавляется, что-то улучшается и вот уже те инструменты работы со строками в Delphi, которые вызывали недоумение своим появлением, например, в Delphi 2009 в Delphi 10.3 Rio начинают играть другими красками. На написание этой статьи меня подтолкнула статья «What’s New» для Delphi 10.3 Rio, а именно то, что разработчики Delphi отдельным пунктом выделили, что давным давно существующий в Delphi класс TStringBuilder оптимизирован и даже обзавелся перегруженным методом ToString использование которого может увеличить производительность. Вот я и решил проверить — на сколько же выросла производительность TStringBuilder в Delphi по сравнению с тем, что было в далеком 2008 году, когда не было ни поддержки мобильных платформ, ни x64 с Linux.

Справка по TStringBuilder

TStringBuilder — это специализированный класс для работы со строками в Delphi (аналог класса StringBuilder в .NET Framework).

TStringBuilder поддерживает выполнение таких операций со строками как конкатенация (сложение), поиск, заменена и вставка подстрок. При этом, массив символов может быть запрошен по индексу или преобразован в строку для сравнения.

На массив символов можно ссылаться напрямую, используя свойство Chars. Свойство Length содержит текущую длину массива символов.

Работать с классом TStringBuilder в Delphi достаточно просто:

var SB: TStringBuilder;
    Len: integer;
begin
  //создаем объект TStringBuilder
  SB:=TStringBuilder.Create('Hello');
  try
    //добавляем к строке подстроку - получим строку "Hello, world!"
    SB.Append(', world!');
    //показываем подстроку пользователю
    ShowMessage(SB.ToString);
    //получаем первый символ  - символ "H"
    ShowMessage(SB.Chars[0]);
    //Получаем длину строки
    Len:=SB.Length;
    //удаляем из строки первые семь символов - останется строка "world!"
    SB.Remove(0,7);
    ShowMessage(SB.ToString);
  finally
    FreeAndNil(SB)
  end;
end;

Обратить внимание стоит на то, что TStringBuilder работает с 0-индексированными (0-based) строками — первый символ имеет индекс 0, а не 1, как мы привыкли в Delphi. В остальном же, работа с TStringBuilder основана на использовании следующих методов:

Append — добавить подстроку к строке (конкатенация строк). Метод перегружен поэтому, Вы можете добавлять к строке числа, массивы символов и другие типы, например так:

SB.Append(123).    //добавить к строке число integer
   Append('A').    //добавить к строке символ
   Append(True)    //добавили к строке boolean

Insert — вставить подстроку в строку с заданной позиции (метод также перегружен)

Replace — заменить подстроку (или символ) на другую строку (символ). Заменяет все вхождения подстрок.

CopyTo — копирование части строки в строку-приемник.

В Delphi 10.3 Rio TStringBuilder обзавелся также перегруженным методом ToString:

function ToString(UpdateCapacity: Boolean): string; reintroduce; overload;

По сообщению разработчиков Delphi, ToString (True) даст лучшую производительность, если больше не ожидается никаких изменений для TStringBuilder, поскольку это уменьшает объем копируемых данных.

Со всеми методами TStringBuilder можно ознакомиться в справке Delphi.

Меня же больше интересует насколько TStringBuilder стал более производительным в части конкатенации строк по сравнению с обычной (привычной, классической) операцией сложения?

Вводные для теста производительности TStringBuilder

Итак, что у меня имеется для тестирования TStringBuilder:

Стационарный компьютер имеет следующие характеристики:

  • Процессор Intel Core i5 8400 (6-ти ядерный)
  • ОЗУ: 16 ГБ
  • ОС: Windows 10 x64

Мобильное устройство:

Смартфон LG Q7+ (Android 8.1.0)

Модель процессора MediaTek MT6750S
Частота процессора 1.5 ГГц
Кол-во ядер процессора 8
Оперативная память 3 ГБ

Проверять будем конкатенацию (сложение) строк Delphi в следующем порядке:

  1. Проверяем скорость сложения строк с использованием метода Append TStringBuilder
  2. Проверяем обычное сложение строк (Str1+Str2)
  3. Проверяем скорость сложения строк с использованием метода Append TStringBuilder и вывод строки обновленным методом ToString(True).

Все три проверки буду проводить на всех доступных устройствах.

Приложение для тестирование производительности TStringBuilder

Версия для ОС Windows

Внешний вид приложения для теста производительности TStringBuilder в Windows:

Каждую операцию сложения строк будем повторять по десять раз и результаты теста выводить в TStringGrid.

Код обработчика OnClick:

procedure TForm6.Button1Click(Sender: TObject);
var
  InitialString: string;
  FinalString: String;
  ConcatStr: string;
  Index: integer;
  StringBuilder: TStringBuilder;
  Limit: integer;
  I: integer;
  t1,t2,t3: TStopwatch;
begin
  Limit := udLimit.Position;
  InitialString := edStr1.Text;
  ConcatStr:=edStr2.Text;
  //повторяем тест 10 раз
  for I := 0 to 9 do
  begin
    StringGrid1.Cells[0,i+1]:=(i+1).ToString;
    //проверка TStringBuiler #1
    t1:=TStopwatch.StartNew;//засекаем время
    //используем TStringBuilder для сложения двух строк
    StringBuilder := TStringBuilder.Create(InitialString, InitialString.Length + Limit*ConcatStr.Length);
    try
      for Index := 0 to Limit - 1 do
        StringBuilder.Append(ConcatStr);
      //используем старый метод ToString
      FinalString := StringBuilder.ToString();
      t1.Stop;
      //Выводим время выполнения операций, мс
      StringGrid1.Cells[1,i+1]:=t1.ElapsedMilliseconds.ToString;
    finally
      StringBuilder.Free;
    end;
 
    FinalString:=EmptyStr;
 
    //проверка TStringBuiler #2
    t2:=TStopwatch.StartNew; //засекаем время
    //используем TStringBuilder для сложения двух строк
    StringBuilder := TStringBuilder.Create(InitialString, InitialString.Length + Limit*ConcatStr.Length);
    try
      for Index := 0 to Limit - 1 do
        StringBuilder.Append(ConcatStr);
      FinalString := StringBuilder.ToString(True);
      t2.Stop;
      //Выводим время выполнения операций, мс
      StringGrid1.Cells[3,i+1]:=t2.ElapsedMilliseconds.ToString;
    finally
      StringBuilder.Free;
    end;
 
    //проверка обычного сложения строк
    FinalString := InitialString;
    t3:=TStopwatch.StartNew; //засекаем время
    for Index := 0 to Limit - 1 do
    begin
      FinalString := FinalString + ConcatStr;
    end;
    t3.Stop;
    //Выводим время выполнения операций, мс
    StringGrid1.Cells[2,i+1]:=t3.ElapsedMilliseconds.ToString;
 
    Application.ProcessMessages;
  end;
end;
Результаты теста TStringBuilder в Windows

Тестирование сложение двух строк

  • Строка 1: edStr1
  • Строка 2: edStr2
  • Количество сложений: 30 000 000
# TStringBuilder Классика TStringBuilder.ToString(True)
1 234 483 165
2 249 527 163
3 197 482 155
4 206 495 157
5 186 503 155
6 185 503 162
7 188 499 155
8 188 519 155
9 187 478 155
10 208 537 154

Прибавление к строке одного символа

  • Строка 1: edStr1
  • Строка 2: a
  • Количество сложений: 30 000 000
# TStringBuilder Классика TStringBuilder.ToString(True)
1 149 314 124
2 149 318 128
3 137 332 207
4 122 312 123
5 121 311 123
6 146 312 173
7 130 327 124
8 122 315 124
9 122 317 123
10 122 312 123

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

Версия для ОС Android

Внешний вид приложения для тестирования TStringBuilder:

Исходный код программы тот же, что и в версии для Windows с одним лишь исключением — результаты теста будут выводиться в обычный Memo:

procedure TForm7.CornerButton1Click(Sender: TObject);
var
  InitialString: string;
  FinalString: String;
  ConcatStr: string;
  Index: integer;
  StringBuilder: TStringBuilder;
  Limit: integer;
  I: integer;
  t1,t2,t3: TStopwatch;
begin
  Limit := Trunc(udLimit.Value);
  InitialString := edStr1.Text;
  ConcatStr:=edStr2.Text;
  //повторяем тест 10 раз
  for I := 0 to 9 do
  begin
    Memo1.Lines.Add('Проход № '+(i+1).ToString);
    //проверка TStringBuiler #1
    t1:=TStopwatch.StartNew;//засекаем время
    //используем TStringBuilder для сложения двух строк
    StringBuilder := TStringBuilder.Create(InitialString, InitialString.Length + Limit*ConcatStr.Length);
    try
      for Index := 0 to Limit - 1 do
        StringBuilder.Append(ConcatStr);
      //используем старый метод ToString
      FinalString := StringBuilder.ToString();
      t1.Stop;
      //Выводим время выполнения операций, мс
      Memo1.Lines.Add('TStringBuiler #1 '+t1.ElapsedMilliseconds.ToString);
    finally
      StringBuilder.Free;
    end;
 
    FinalString:=EmptyStr;
 
    //проверка TStringBuiler #2
    t2:=TStopwatch.StartNew; //засекаем время
    //используем TStringBuilder для сложения двух строк
    StringBuilder := TStringBuilder.Create(InitialString, InitialString.Length + Limit*ConcatStr.Length);
    try
      for Index := 0 to Limit - 1 do
        StringBuilder.Append(ConcatStr);
      FinalString := StringBuilder.ToString(True);
      t2.Stop;
      //Выводим время выполнения операций, мс
      Memo1.Lines.Add('TStringBuiler #2 '+t2.ElapsedMilliseconds.ToString);
    finally
      StringBuilder.Free;
    end;
 
    //проверка обычного сложения строк
    FinalString := InitialString;
    t3:=TStopwatch.StartNew; //засекаем время
    for Index := 0 to Limit - 1 do
    begin
      FinalString := FinalString + ConcatStr;
    end;
    t3.Stop;
    //Выводим время выполнения операций, мс
    Memo1.Lines.Add('Классика '+t3.ElapsedMilliseconds.ToString);
    Application.ProcessMessages;
  end;
end;
Результаты теста TStringBuilder на LG Q7+

Тестирование сложение двух строк

  • Строка 1: edStr1
  • Строка 2: edStr2
  • Количество сложений: 1 000 000
# TStringBuilder Классика TStringBuilder.ToString(True)
1 302 849 232
2 233 847 221
3 231 830 221
4 230 828 224
5 230 831 221
6 238 829 227
7 230 821 221
8 230 821 221
9 230 838 223
10 238 822 228

Прибавление к строке одного символа

  • Строка 1: edStr1
  • Строка 2: a
  • Количество сложений: 1 000 000
# TStringBuilder Классика TStringBuilder.ToString(True)
1 302 776 221
2 221 774 218
3 221 775 219
4 221 776 218
5 221 772 218
6 221 773 218
7 221 773 219
8 221 773 219
9 221 773 219
10 221 772 218

Опять же, как можно видеть по результатам тестирования, TStringBuilder ускоряет работу по сложению двух строк примерно в 2,5 раза, при этом, новый метод ToString(True) дает незначительное ускорение по сравнению с обычным ToString.

Резюмируем

На рисунке ниже представлено среднее время сложения двух строк в Windows.

  1. Синий столбик — сложение двух строк
  2. Оранжевый — прибавление к строке одного символа

То де самое, но уже для Android:

Представленные выше диаграммы наглядно демонстрируют рост производительности TStringBuilder при работе со строками в Delphi 10.3 Rio как в Windows, так и в Android. Между тем, новые метод TStringBuilder.ToStrng(True) дает незначительный прирост производительности по сравнению с ранее существующим методом ToString.

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

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

Исходники обоих тестовых приложений можно скачать со страницы:

Исходники — Прочие
3 2 голоса
Рейтинг статьи
уважаемые посетители блога, если Вам понравилась, то, пожалуйста, помогите автору с лечением. Подробности тут.
Подписаться
Уведомить о
4 Комментарий
Межтекстовые Отзывы
Посмотреть все комментарии
KeeperWorld
KeeperWorld
17/03/2019 05:37

Автор, спасибо большое за обзор и проделанную работу!

Хотел уже переделать некоторые узкие места своей программы на TStringBuilder, но столкнулся с банальной проблемой, а найти в этом классе подстроку — забыли в Эмбаркадеро реализовать? :) И как тогда найти подстроку? Как обычно, через PosEx?

SCHigi
SCHigi
07/07/2020 05:12

файл c исходниками содержит архив исходников потокового аудио…

SCHigi
SCHigi
07/07/2020 05:57

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