From a4eb7962205ed40e3e76a85d5393ee923b034658 Mon Sep 17 00:00:00 2001 From: Aleksandr Orefkov Date: Fri, 8 Aug 2025 12:15:24 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B2=20=D1=82=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=85=20=D0=B8=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/overview.md | 26 ++++++++++++++++---------- readme.md | 1 + tests/test_str.cpp | 18 +++++++++--------- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/docs/overview.md b/docs/overview.md index 247efc9..336ead3 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -43,6 +43,7 @@ для строк — по меньшей мере может приводить к уменьшению производительности, а также нарушает принцип «не плати за то, чем не пользуешься». Почему же «строки строкам рознь» и почему нам мало одного типа для строки, рассмотрим как раз далее. +### Ресурсы И следующий вопрос, возникающий со строками — это владение ресурсами. Практически каждый крупный фреймворк решал эту задачу самостоятельно, изобретая свои велосипеды. У нас есть `std::string`, в QT у нас `QString`, в MFC - `CString`, в ATL - `CAtlString`, свои строки есть в Folly, @@ -50,10 +51,12 @@ Многие из этих реализаций в аспекте управления ресурсами для улучшения производительности использовали подход **COW** – “Copy On Write”. При этом объект строки ссылался на некий разделяемый между несколькими объектами буфер с символами -строки и счётчиком ссылок на этот буфер, что позволяло быстро создавать копию строки. +строки и счётчиком ссылок на этот буфер, что позволяло быстро создавать копию строки, а реально копировать символы только +при её модификации. Но все они совпадали в одном — строка всегда предполагалась мутабельной, то есть что мы можем модифицировать символы в буфере строки. +### Мутабельность / иммутабельность Из-за этого подход **COW** умер к С++11: при каждой операции, могущей модифицировать символы строки приходилось проверять, не ссылаемся ли мы на разделяемый буфер и если да, то копировать символы в другой буфер. В многопоточной же среде потом ещё и проверять, не надо ли теперь освобождать старый буфер, и естественно всё это обмазавшись @@ -65,7 +68,8 @@ “Small String Optimization”, когда объект строки содержит внутри себя небольшой буфер и символы коротких строк располагаются прямо в нём. Но это уже зависит от реализации: в одних библиотеках помещают в объект строки до 15 байт, в некоторых до 23. -Однако эта оптимизация тоже палка о двух концах, и может в различных реализациях сделать строку "non trivial moveable". +Однако эта оптимизация тоже палка о двух концах, и может в различных реализациях усложнить перемещение строки - если она хранит +указатель на свой внутренний буфер, его придётся корректировать. А без COW мутабельность строк приводит к тому, что любая инициализация объекта строки приводит к копированию байтов. Посмотрим такой код: @@ -108,6 +112,7 @@ скопировать его в `std::string`. А раз нужен `std::string`, то и параметром функции оптимальнее cделать `const std::string&` и далее по цепочке, все параметры вновь станут `const std::string&`. +### Конкатенация строк Далее, после инициализации строки, самая частая мутабельная операция с ними, скорее всего конкатенация строк, либо в виде просто сложения строк, либо добавления строки к строке. И именно она легко может вызывать как неоптимальную производительность при неграмотном использовании, так и оверхед по памяти, даже при грамотном использовании. @@ -241,7 +246,7 @@ #### Класс sstring (shared string). (simstr::sstring) -Класс, могущий хранить иммутабельную строку. +Класс, умеющий хранить иммутабельную строку. То есть ему можно присвоить некую строку только целиком, модифицировать символы строки нельзя. Владеет строкой, управляет памятью для символов строки. @@ -397,7 +402,8 @@ } ``` -В этом примере вы наверняка заметили, как конкатенируются строки и задались вопросом — как же при двух сложениях длина всего результата считалась всего один раз, чтобы выделить необходимое место сразу, без промежуточных буферов? +В этом примере вы наверняка заметили, как конкатенируются строки и задались вопросом — как же при двух сложениях считалась +длина всего результата, чтобы выделить необходимое место сразу за один раз, без промежуточных буферов? Ответ на этот вопрос: @@ -433,12 +439,12 @@     constexpr symb_type* place(symb_type* p) const noexcept { return b.place(a.place(p)); } }; ``` - -Таким образом, операция сложения нескольких строковых выражений создает объект, также являющийся строковым выражением, -который рекурсивно хранит ссылки на слагаемые части, каждая из которых знает свой размер и умеет размещать себя -в буфере результата. А чтобы получить готовый результат сложения, это выражение надо "материализовать", то есть -присвоить любому владеющему стровому объекту, который выделит всю память под результат за один раз и запустит -размещение символов в этой памяти. +Таким образом, операция сложения строковых выражений создает объект, также являющийся строковым выражением, +к которому также может быть применена следующая операция сложения, и который рекурсивно хранит ссылки на слагаемые части, +каждая из которых знает свой размер и умеет размещать себя в буфере результата. И так далее, к каждому получаемому +строковому выражению можно снова применить `operator +`, формируя цепочку из нескольких строковых выражений, +и в итоге "материализовать" последний получившийся объект, который сначала посчитает размер всей общей памяти для +конечного результата, а затем разместит вложенные подвыражения в один буфер. Все строковые типы библиотеки сами являются строковыми выражениями, то есть могут служить слагаемыми в конкатенациях строковых выражений. diff --git a/readme.md b/readme.md index 21ccc40..634ef5d 100644 --- a/readme.md +++ b/readme.md @@ -84,6 +84,7 @@ Windows и Linux (в WSL), с использованием компилятор ## Примеры использования Пока отдельных примеров использования не подготовлено, можно посмотреть тексты [тестов](tests/test_str.cpp), [бенчмарков](bench/bench_str.cpp), и [утилиты подготовки html](bench/process_result.cpp) из результатов бенчмарков. +Также simstr используется в моём проекте [v8sqlite](https://github.com/orefkov/v8sqlite) ## Сгенерированная документация [Находится здесь](https://snegopat.ru/simstr/docs/) diff --git a/tests/test_str.cpp b/tests/test_str.cpp index 94de3d9..a2d60fb 100644 --- a/tests/test_str.cpp +++ b/tests/test_str.cpp @@ -764,9 +764,9 @@ TEST(SimStr, CreateSstringLong) { } TEST(SimStr, CreateSstringStrExpr) { - EXPECT_EQ(stringa{eea + "test" + 101 + e_c(3, 'a')}, "test101aaa"); - EXPECT_EQ(stringu{eeu + u"test" + 1234 + e_c(3, u'a')}, u"test1234aaa"); - EXPECT_EQ(stringw{eew + L"test" + 12345 + e_c(3, L'a')}, L"test12345aaa"); + EXPECT_EQ(stringa{"test"_ss + 101 + e_c(3, 'a')}, "test101aaa"); + EXPECT_EQ(stringu{u"test"_ss + 1234 + e_c(3, u'a')}, u"test1234aaa"); + EXPECT_EQ(stringw{L"test"_ss + 12345 + e_c(3, L'a')}, L"test12345aaa"); } TEST(SimStr, CreateSstringReplace) { @@ -788,8 +788,8 @@ TEST(SimStr, AssignSstring) { EXPECT_EQ(test = test, "test"); EXPECT_EQ(test = "next", "next"); EXPECT_EQ(test = ssa{"other"}, "other"); - EXPECT_EQ(test = stringa{eea + "trtr" + 10}, "trtr10"); - EXPECT_EQ(test = eea + "trtr" + 20, "trtr20"); + EXPECT_EQ(test = stringa{"trtr"_ss + 10}, "trtr10"); + EXPECT_EQ(test = "trtr"_ss + 20, "trtr20"); EXPECT_EQ(test = test(2), "tr20"); EXPECT_EQ(test = lstringa<10>{"func"}, "func"); EXPECT_EQ(test = lstringsa<10>{"func"}, "func"); @@ -1025,9 +1025,9 @@ TEST(SimStr, LStrAppend) { EXPECT_EQ(lstringa<20>{"test"}.append("ing"), "testing"); EXPECT_EQ(lstringa<20>{"test"}.append("ing"_ss + 10), "testing10"); EXPECT_EQ(lstringa<20>{"test"}.append_in(3, "ing"), "tesing"); - EXPECT_EQ(lstringa<20>{"test"}.append_in(3, eea + "ing" + 10), "tesing10"); + EXPECT_EQ(lstringa<20>{"test"}.append_in(3, "ing"_ss + 10), "tesing10"); EXPECT_EQ(lstringa<20>{"test"} += "ing", "testing"); - EXPECT_EQ(lstringa<20>{"test"} += eea + "ing" + 10, "testing10"); + EXPECT_EQ(lstringa<20>{"test"} += "ing"_ss + 10, "testing10"); } TEST(SimStr, LStrChange) { @@ -1287,7 +1287,7 @@ TEST(SimStr, LStrFormat) { } } -TEST(SimStr, LStrFormatExpressions) { +TEST(SimStr, LStrJoinAndExpressions) { lstringa<100> buffer; buffer = e_join(std::vector{}, "<>"); EXPECT_EQ(buffer, ""); @@ -1524,7 +1524,7 @@ struct bbbb { res = "my_str: "_ss + field + '\n'; auto strs = get_strings(); for (const auto& s : strs) { - res += e_repl(s, "f", "--") + e_choice(&s != &strs.back(), "\n"_ss, eea); + res += e_repl(s, "f", "--") + e_if(&s != &strs.back(), "\n"); } }