mirror of https://github.com/orefkov/simstr.git
347 lines
28 KiB
Markdown
347 lines
28 KiB
Markdown
# simstr - библиотека строковых объектов и функций
|
||
<h2>Ускорь работу со строками в 2-10 раз!</h2>
|
||
|
||
[](https://github.com/orefkov/simstr/actions/workflows/cmake-multi-platform.yml)
|
||
|
||
Версия 1.6.0.
|
||
|
||
<span class="obfuscator"><a href="readme.md">On English | По-английски</a></span>
|
||
|
||
В этой библиотеке содержится современная реализация нескольких видов строковых объектов и различных алгоритмов для работы со строками.
|
||
|
||
Цель библиотеки - сделать работу со строками в С++ такой же простой и лёгкой, как во множестве других языков, особенно
|
||
скриптовых, но при этом сохранив оптимальность и производительность на уровне С и C++, и даже улучшив их.
|
||
|
||
Не секрет, что работа со строками в С++ зачастую доставляет боль. Класс `std::string` часто неудобен либо неэффективен.
|
||
Многих функций, обычно необходимых при работе со строками, просто нет, и их каждому приходится писать самому.
|
||
Даже элементарно конкатенировать `std::string` и `std::string_view` стало возможно только с C++26.
|
||
Именно поэтому я начал примерно в 2012 году создавать для себя эту библиотеку, и теперь готов поделится ею со всеми C++ разработчиками.
|
||
|
||
Эта библиотека не делалась как универсальный комбайн, который "может всё", я реализовывал то, что мне приходилось
|
||
использовать в работе, стараясь сделать это наиболее эффективным способом, и скромно надеюсь, что кое-что у меня получилось
|
||
и пригодится другим людям, либо напрямую, либо как источник идей.
|
||
|
||
Библиотека содержит две части:
|
||
- Реализация [*"Строковых выражений"*](https://orefkov.github.io/simstr/docs_ru/overview.html#autotoc_md27) и алгоритмов работы
|
||
с константными строками.\
|
||
Для использования этой части достаточно просто взять файл `"include/simstr/strexpr.h"` и написать в своём коде
|
||
```cpp
|
||
#include "путь/к файлу/strexpr.h"
|
||
```
|
||
Это позволит вам для стандартных строковых типов (`std::basic_string`, `std::basic_string_view`) использовать
|
||
мощные и быстрые *"строковые выражения"* для конкатенации и построения строк, а также упрощенные варианты классов `simple_str` и
|
||
`simple_str_nt`, которые реализуют все те строковые алгоритмы библиотеки, которые не требуют хранения или модификации строк.
|
||
Так как это header-only часть, она не включает в себя работу с UTF-кодировками и упрощённый Unicode.
|
||
- Полная версия, требующая подключения всей библиотеки (`"include/simstr/sstring.h"`), добавляет свои строковые типы с
|
||
возможностями хранения и модификации строк, работает с UTF-кодировками и упрощённым Unicode.
|
||
|
||
Библиотека не претендует на роль "поменял хедер и всё заработало лучше" - она прекрасно уживается вместе со стандартными строками
|
||
и не меняет поведение уже существующего кода, работающего с ними. Многие методы в ней я старался делать совместимыми
|
||
с `std::string` и `std::string_view`, но особо с этим не заморачивался. Переписывание вашего кода на работу с `simstr`
|
||
потребует некоторых усилий, но уверяю, что они окупятся. А благодаря совместимости со стандартными строками эту работу можно делать
|
||
поэтапно, небольшими кусками. Новый же код работы со строками создавать с её применением легко и доставляет удовольствие :)
|
||
|
||
|
||
Основное отличие `simstr` от `std::string` - для работы со строками используется не единый универсальный класс, а несколько
|
||
видов объектов, каждый из которых хорош для своих целей, и при этом хорошо взаимодействующих друг с другом.
|
||
Если вы активно использовали `std::string_view` и понимали, в чём его преимущества и недостатки по сравнению с `std::string`,
|
||
то подход `simstr` вам также будет понятен.
|
||
|
||
## Основные возможности библиотеки
|
||
При использовании только `#include "simstr/strexpr.h"`:
|
||
- Поддержка работы со строками `char`, `char8_t`, `char16_t`, `char32_t`, `wchar_t`.
|
||
- Мощная и расширяемая система *"Строковых выражений"*.
|
||
Позволяет эффективно реализовать преобразование и сложение (конкатенацию) строк, строковых литералов, чисел (и возможно других объектов),
|
||
добиваясь значительного ускорения строковых операций.
|
||
Совместима как со строковыми объектами `simstr`, так и со стандартными строками (`std::basic_string`,
|
||
`std::basic_string_view`), что позволяет применять быструю конкатенацию и там, где пока не получается отказаться от стандартных строк.
|
||
Тоже позволяет смешивать в операциях строки совместимых типов символов.
|
||
- Константные строковые функции (не меняют исходную строку):
|
||
- Получение подстрок.
|
||
- Сравнение строк, сравнение строк без учёта регистра ASCII-символов.
|
||
- Поиск подстрок и символов - с начала или с конца строки.
|
||
- Различный тримминг строк - справа, слева, везде, по пробельным символам, по заданным символам.
|
||
- Замена подстрок (созданием копии строки с заменой).
|
||
- Замена набора символов на набор соответствующих подстрок (созданием копии строки с заменой).
|
||
- Слияние (join) контейнеров строк в единую строку, с заданием разделителей и опций - "пропускать пустые", "разделитель после последней".
|
||
- Разбиение (split) строк на части по заданному разделителю. Разбиение возможно сразу в контейнер со строками, либо вызовом функтора для
|
||
каждой подстроки, либо путем итерации с помощью итератора `Splitter`.
|
||
- Функции модификации стандартных строк строковыми выражениями:
|
||
- str::append, str::prepend, str::insert, str::change - меняют str::string строковыми выражениями,
|
||
например `str::append(text, "count = "_ss + count)`.
|
||
- str::replace - заменяет вхождения искомой подстроки на строку замены или строковое выражение.
|
||
Если подстрока не найдена, строковое выражение даже не вычисляется.
|
||
- Парсинг целых чисел с возможностью "тонкой" настройки при компиляции - можно задавать опции проверки переполнения,
|
||
пропуск пробельных символов, конкретное основание счисления либо автовыбор по префиксам `0x`, `0`, `0b`, `0o`,
|
||
допустимость знака `+`. Парсинг реализован для всех видов строк и символов.
|
||
- Парсинг double для `char` и `wchar_t`, а также совместимых с ними по размеру типов символов.
|
||
|
||
При использовании полной версии библиотеки:
|
||
- Всё то же, что и перечислено выше, плюс
|
||
- Дополнительные эффективные строковые объекты - `sstring` (shared string), `lstring` (local string).
|
||
- `lstring` - поддерживает множество мутабельных операций со строками - различные замены, вставки, удаления и т.п.
|
||
Позволяет задавать размер для внутреннего буфера символов, что может превращать *Small String Optimization* в *Big String Optimization* :).
|
||
- Прозрачное преобразование строк из одного типа символов в другой, с автоматической конвертацией между UTF-8, UTF-16, UTF-32,
|
||
используя [simdutf](https://github.com/simdutf/simdutf). Строки "совместимых" типов преобразуются простым копированием:
|
||
`char <-> char8_t`, `wchar_t <-> char32_t` в Linux, `wchar_t <-> char16_t` в Windows.
|
||
- Интеграция с функциями форматирования `format` и `sprintf` (с автоматическим увеличением буфера).
|
||
Форматирование возможно для строк `char`, `wchar_t` и строк, совместимых с ними по размеру.
|
||
То есть под Windows это `char8_t`, `char16_t`, под Linux - `char8_t`, `char32_t` (писать свою библиотеку форматирования для всех видов
|
||
символов не входило в мои замыслы).
|
||
- Содержится минимальная поддержка Unicode при преобразовании `upper`, `lower` и регистро-независимом сравнении строк.
|
||
Работает только для символов первой плоскости Unicode (до 0xFFFF), а при смене регистра не учитываются случаи, когда один code point
|
||
может преобразовываться в несколько, то есть преобразование регистра символов соответствует `std::towupper`, `std::towlower` для unicode локали, только быстрее и может работать с любым видом символов.
|
||
- Реализован `hash map` для ключей строкового типа, на базе `std::unordered_map`, с возможностью более эффективного хранения и
|
||
сравнения ключей по сравнению с ключами `std::string`. Поддерживается возможность регистро-независимого сравнения ключей (Ascii или
|
||
минимальный Unicode (см. предыдущий пункт)).
|
||
|
||
## Строковые выражения
|
||
Это специальные объекты, которые эффективно реализуют конкатенацию строк, с помощью `operator+`.
|
||
Главный принцип, за счёт которого достигается эффективная работа - сколько бы операндов не входило во всё выражение,
|
||
никаких временных (промежуточных) строк не создаётся, общая длина всего результата подсчитывается только один раз,
|
||
один раз выделяется память под буфер символов результата, после чего символы копируются сразу в буфер результата
|
||
на своё место. Никаких перевыделений памяти, никакого передвигания символов в различных промежуточных буферах - всё
|
||
максимально эффективно. Благодаря возможностям шаблонов C++ и перегрузке операторов, выражение пишется максимально
|
||
приближённо к обычному синтаксису сложения строк.
|
||
Кроме того, есть специальные перегрузки для сложения строковых объектов и строковых литералов, строк и чисел,
|
||
для копирования с заменой, для слияния контейнеров строк и многое другое.
|
||
Благодаря расширяемости этой системы - возможно создание новых вариантов построения строк, развитие постоянно продолжается.
|
||
|
||
Все строковые объекты из `simstr` - сами являются строковыми выражениями, то есть их можно использовать в операциях конкатенации
|
||
строковых выражений напрямую. Стандартные строки (`std::basic_string`, `std::basic_string_view`) - тоже могут служить операндами
|
||
в операциях сложения со строковыми выражениями. Либо их можно легко преобразовать в строковое выражение, поставив перед ними
|
||
унарный `+`.
|
||
|
||
## Примеры использования
|
||
### Сложение строк с числами
|
||
```cpp
|
||
std::string s1 = "start ";
|
||
int i;
|
||
....
|
||
// Было
|
||
std::string str = s1 + std::to_string(i) + " end";
|
||
// Стало
|
||
std::string str = +s1 + i + " end";
|
||
```
|
||
`+s1` - преобразует `std::string` в объект - строковое выражение, для которого есть эффективная конкатенация с числами и строковыми литералами.
|
||
|
||
По бенчмаркам [ускорение 1.6 - 2 раза](https://orefkov.github.io/simstr/results.html#bs70109915512075798510).
|
||
|
||
### Сложение строк с числами в hex-формате
|
||
```cpp
|
||
....
|
||
// Было
|
||
std::string str = s1 + std::format("0x{:x}", i) + " end";
|
||
// Стало
|
||
std::string str = +s1 + e_hex<HexFlags::Short>(i) + " end";
|
||
```
|
||
Ускорение в [**9 - 14 раз!!!**](https://orefkov.github.io/simstr/results.html#bs146911715078927772520)
|
||
|
||
### Сложение нескольких литералов и поиск в `std::string_view`
|
||
```cpp
|
||
// Было так
|
||
size_t find_pos(std::string_view src, std::string_view name) {
|
||
// before C++26 we can not concatenate string and string_view...
|
||
return src.find("\n- "s + std::string{name} + " -\n");
|
||
}
|
||
// При использовании только "strexpr.h" стало так
|
||
size_t find_pos(ssa src, ssa name) {
|
||
return src.find(std::string{"\n- " + name + " -\n"});
|
||
}
|
||
|
||
// А при использовании полной библиотеки можно сделать так
|
||
size_t find_pos(ssa src, ssa name) {
|
||
// В этом варианте если результат конкатенации вмещается в 207 символов - она производится в буфере на стеке,
|
||
// без алокации и освобождения памяти, ускорение в несколько раз. И только если результат длиннее 207 символов -
|
||
// будет всего одна аллокация, и конкатенация будет сразу в алоцированный буфер, без перекопирования символов.
|
||
return src.find(lstringa<200>{"\n- " + name + " -\n"});
|
||
}
|
||
```
|
||
`ssa` - псевдоним для `simple_str<char>` - аналог `std::string_view`, позволяет с минимальными расходами принимать параметром функции
|
||
любой строковый объект, который не нужно модифицировать или передавать в C-API: `std::string`, `std::string_view`, `"строковый литерал"`,
|
||
`simple_str_nt`, `sstring`, `lstring`. Также так как он при этом является ещё и "строковым выражением", то позволяет легко
|
||
строить конкатенации с его участием.
|
||
|
||
По замерам [ускорение 1.5 - 9 раз](https://orefkov.github.io/simstr/results.html#bs68116594352702954700).
|
||
|
||
### Сложение с условиями
|
||
```cpp
|
||
// Было
|
||
std::string buildTypeName(std::string_view type_name, size_t prec, size_t scale) {
|
||
std::string res{type_name};
|
||
if (prec) {
|
||
res += "(" + std::to_string(prec);
|
||
if (scale) {
|
||
res += "," + std::to_string(scale);
|
||
}
|
||
res += ")";
|
||
}
|
||
return res;
|
||
}
|
||
// Стало при использовании только strexpr.h и желании использовать только стандартные строки
|
||
std::string buildTypeName(std::string_view type_name, size_t prec, size_t scale) {
|
||
if (prec) {
|
||
// + превращает type_name из string_view в строковое выражение
|
||
return +type_name + "(" + prec + e_if(scale, ","_ss + scale) + ")";
|
||
}
|
||
return type_name;
|
||
}
|
||
// Стало при использовании только strexpr.h и simple_str строки
|
||
std::string buildTypeName(ssa type_name, size_t prec, size_t scale) {
|
||
if (prec) {
|
||
// ssa уже является строковым выражением, + перед ним не нужен
|
||
return type_name + "(" + prec + e_if(scale, ","_ss + scale) + ")";
|
||
}
|
||
return type_name;
|
||
}
|
||
// Стало при использовании полной библиотеки
|
||
stringa buildTypeName(ssa type_name, size_t prec, size_t scale) {
|
||
if (prec) {
|
||
return type_name + "(" + prec + e_if(scale, ","_ss + scale) + ")";
|
||
}
|
||
return type_name;
|
||
}
|
||
```
|
||
При `prec != 0`, [ускорение 1.5 - 2.2 раза](https://orefkov.github.io/simstr/results.html#bs145290966789248325200).
|
||
|
||
### Сложение с заменами
|
||
```cpp
|
||
// Было
|
||
// Стандартной аналога функции replace из других ЯП нет, напишем свою "в лоб".
|
||
std::string str_replace(std::string_view from, std::string_view pattern, std::string_view repl) {
|
||
std::string result;
|
||
for (size_t offset = 0;;) {
|
||
size_t pos = from.find(pattern, offset);
|
||
if (pos == std::string::npos) {
|
||
result += from.substr(offset);
|
||
break;
|
||
}
|
||
result += from.substr(offset, pos - offset);
|
||
result += repl;
|
||
offset = pos + pattern.length();
|
||
}
|
||
return result;
|
||
}
|
||
|
||
std::string make_str_str(std::string_view from, std::string_view pattern, std::string_view repl) {
|
||
return "<" + str_replace(from, pattern, repl) + ">";
|
||
}
|
||
// Стало - копирование с заменами
|
||
std::string make_str_exp(std::string_view from, std::string_view pattern, std::string_view repl) {
|
||
return "<" + e_repl(from, pattern, repl) + ">";
|
||
}
|
||
```
|
||
[Ускорение от 1.5 раз и выше](https://orefkov.github.io/simstr/results.html#bs54035654251116789780) - в зависимости от содержимого строк.
|
||
|
||
### Разбиение строк на части, парсинг чисел
|
||
```cpp
|
||
// Было - разбить строку по разделителю и подсчитать сумму чисел
|
||
int split_and_calc_total_str(std::string_view numbers, std::string_view delimiter) {
|
||
int total = 0;
|
||
for (size_t start = 0; start < numbers.length(); ) {
|
||
int delim = numbers.find(delimiter, start);
|
||
if (delim == std::string::npos) {
|
||
delim = numbers.size();
|
||
}
|
||
std::string part{numbers.substr(start, delim - start)};
|
||
total += std::strtol(part.c_str(), nullptr, 0);
|
||
start = delim + delimiter.length();
|
||
}
|
||
return total;
|
||
}
|
||
// Стало
|
||
int split_and_calc_total_sim(ssa numbers, ssa delimiter) {
|
||
int total = 0;
|
||
for (auto splitter = numbers.splitter(delimiter); !splitter.is_done();) {
|
||
total += splitter.next().as_int<int>();
|
||
}
|
||
return total;
|
||
}
|
||
```
|
||
[Ускорение в 2-3 раза](https://orefkov.github.io/simstr/results.html#bs7106975351756760120).
|
||
|
||
Помимо приведённых здесь отдельных примеров, можно посмотреть исходники:
|
||
- [тестов всей библиотеки](https://github.com/orefkov/simstr/blob/main/tests/test_str.cpp)
|
||
- [тестов только strexpr части](https://github.com/orefkov/simstr/blob/main/tests/test_expr_only.cpp)
|
||
- [бенчмарков](https://github.com/orefkov/simstr/blob/main/bench/bench_str.cpp)
|
||
- [утилиты подготовки html](https://github.com/orefkov/simstr/blob/main/bench/process_result.cpp) из результатов бенчмарков.
|
||
|
||
## Основные объекты библиотеки
|
||
Доступны при любом использовании:
|
||
- `simple_str<K>` - самая простая строка (или кусок строки), иммутабельная, не владеющая, аналог `std::string_view`.
|
||
- `simple_str_nt<K>` - то же самое, только заявляет, что заканчивается 0. Для работы со сторонними C-API.
|
||
|
||
Доступны при использовании всей библиотеки:
|
||
- `sstring<K>` - shared string, иммутабельная, владеющая, с разделяемым буфером символов, поддержка SSO.
|
||
- `lstring<K, N>` - local string, мутабельная, владеющая, с задаваемым размером SSO буфера.
|
||
|
||
При подключении только `strexpr.h` - типы `simple_str<K>` и `simple_str_nt<K>` не содержат методов для работы с UTF и Unicode.
|
||
|
||
## Статьи
|
||
- [Обзор и введение](docs/overview_ru.md)
|
||
- [Обзорная статья на Хабре](https://habr.com/ru/articles/935590)
|
||
- [Описание применяемой техники "Expression Templates"](https://habr.com/ru/articles/936468/)
|
||
|
||
## Использование
|
||
Библиотеку можно использовать частично, просто взяв файл `"include/simstr/strexpr.h"` и включив в свои исходники
|
||
```cpp
|
||
#include "include/simstr/strexpr.h"
|
||
```
|
||
Это подключит только строковые выражения и упрощённые реализации `simple_str` и `simple_str_nt`, без функций работы с UTF и Unicode.
|
||
|
||
Полная же версия библиотеки `simstr` состоит из трёх заголовочных файлов и двух исходников.
|
||
Можно подключать как CMake проект через `add_subdirectory` (библиотека `simstr`),
|
||
можно просто включить файлы в свой проект. Для сборки также требуется [simdutf](https://github.com/simdutf/simdutf) (при использовании CMake
|
||
скачивается автоматически).
|
||
|
||
### Подключение через FetchContent
|
||
```
|
||
function(add_simstr)
|
||
set(SIMSTR_BUILD_TESTS OFF)
|
||
set(SIMSTR_BENCHMARKS OFF)
|
||
FetchContent_Declare(
|
||
simstr
|
||
GIT_REPOSITORY https://github.com/orefkov/simstr.git
|
||
GIT_SHALLOW TRUE
|
||
GIT_TAG tags/rel1.6.0 # Укажите нужный релиз
|
||
FIND_PACKAGE_ARGS NAMES simstr 1.6.0
|
||
)
|
||
FetchContent_MakeAvailable(simstr)
|
||
endfunction()
|
||
|
||
add_simstr()
|
||
|
||
target_link_libraries(<your target> PUBLIC simstr::simstr)
|
||
```
|
||
|
||
Библиотека также включена в [vcpkg](https://vcpkg.io), подключается как `orefkov-simstr`.
|
||
|
||
Для работы `simstr` требуется компилятор стандарта не ниже С++20 - используются концепты и std::format.
|
||
Работа проверялась под Windows на MSVC-19 и Clang-19, под Linux - на GCC-13 и Clang-21.
|
||
Также проверялась работа в WASM, сборка в Emscripten 4.0.6, Clang-21.
|
||
|
||
|
||
## Удобная отладка
|
||
Вместе с библиотекой поставляются два файла, делающие просмотр simstr строковых объектов в отладчиках
|
||
более удобным.\
|
||
Более подробно описано [здесь](for_debug/readme_ru.md).
|
||
|
||
## Бенчмарки
|
||
Бенчмарки производятся с использованием фреймворка [Google benchmark](https://github.com/google/benchmark).
|
||
Постарался сделать замеры для наиболее типичных операций, встречающихся в обычной работе. Я проводил замеры на своём оборудовании, под
|
||
Windows и Linux (в WSL), с использованием компиляторов MSVC, Clang, GCC. Сторонние результаты приветствуются.
|
||
Также проводил замеры в WASM, сборка в Emscripten. Обращаю внимание, что под WASM в Emscripten собирается 32-битная сборка, а значит,
|
||
размеры буферов SSO в объектах меньше.
|
||
|
||
- [Исходный код бенчмарков](bench/bench_str.cpp)
|
||
- [Результаты бенчмарков](https://orefkov.github.io/simstr/results.html)
|
||
|
||
Также simstr используется в моих проектах:
|
||
- [simjson](https://github.com/orefkov/simjson) - библиотека для простой работы с JSON с использованием строк simstr.
|
||
- [simrex](https://github.com/orefkov/simrex) - обёртка для работы с регулярными выражениями [Oniguruma](https://github.com/kkos/oniguruma) с использованием строк simstr.
|
||
- [v8sqlite](https://github.com/orefkov/v8sqlite) - внешняя компонента для 1С-Предприятия V8 для работы с sqlite.
|
||
|
||
|
||
## Сгенерированная документация
|
||
[Находится здесь](https://orefkov.github.io/simstr/docs_ru/)
|