Работа со строками в 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 в следующем порядке:
- Проверяем скорость сложения строк с использованием метода Append TStringBuilder
- Проверяем обычное сложение строк (Str1+Str2)
- Проверяем скорость сложения строк с использованием метода 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 Cantu — TStringBuilder оказывался намного медленнее.
Версия для ОС 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.
- Синий столбик — сложение двух строк
- Оранжевый — прибавление к строке одного символа
То де самое, но уже для Android:
Представленные выше диаграммы наглядно демонстрируют рост производительности TStringBuilder при работе со строками в Delphi 10.3 Rio как в Windows, так и в Android. Между тем, новые метод TStringBuilder.ToStrng(True) дает незначительный прирост производительности по сравнению с ранее существующим методом ToString.
Таким образом, если в вашем приложении Delphi часто приходится иметь дело со строками, то, думаю, что имеет смысл обратит внимание на работу с TStringBuilder вне зависимости от того в какой операционной системе вы работаете.
Остается открытым вопрос о производительности TStringBuilder при работе в многопоточных приложениях, но это уже отдельная тема повышения производительности работы со строками в Delphi в принципе.
Исходники обоих тестовых приложений можно скачать со страницы:
Автор, спасибо большое за обзор и проделанную работу!
Хотел уже переделать некоторые узкие места своей программы на TStringBuilder, но столкнулся с банальной проблемой, а найти в этом классе подстроку — забыли в Эмбаркадеро реализовать? :) И как тогда найти подстроку? Как обычно, через PosEx?
KeeperWorld, всегда пожалуйста :)
По поводу поиска подстроки, видимо, да — использовать PosEx или TStringHelper’ом пользоваться, хотя то на то и выйдет.
файл c исходниками содержит архив исходников потокового аудио…
но, честно говоря, в тесте есть читерство в пользу TStringBuilder — ему сходу задается длина строки, он сразу выделяет память и больше не тратит на это время.