TGViewer
this->notes. this->notes. @thisnotes · 4.51K subscribers
Post #36 355

Forwarded from Experimental chill

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

К сожалению, сейчас в С++ нет максимально быстрого способа поджойнить строки, чтобы не платить оверхеда. И по-моему сейчас нет ни в одном языке библиотечной поддержки так делать:

Одни скажут, что можно сделать reserve и сделать append, но тогда append будет каждый раз проверять на то, нужно ли расширять буффер, будет добавлять нулевой байтик в конец, потому что строки в С++ заканчиваются нулевым байтом и будут обновлять размер:

std::string GeneratePattern(const std::string& pattern, size_t count) {
std::string ret;
ret.reserve(pattern.size() * count);
for (size_t i = 0; i < count; i++) {
// SUB-OPTIMAL:
// * Writes 'count' nulls
// * Updates size and checks for potential resize 'count' times
ret.append(pattern);
}
return ret;
}

Другие скажут, что можно сделать resize и потом memcpy, но, к сожалению, resize должен как-то проинициализировать память, поэтому будет лишний memset:

std::string GeneratePattern(const std::string& pattern, size_t count) {
std::string ret;
const auto step = pattern.size();
// SUB-OPTIMAL: We memset step*count bytes
// only to overwrite them.
ret.resize(step * count);
for (size_t i = 0; i < count; i++) {
// GOOD: No bookkeeping
memcpy(ret.data() + i * step, pattern.data(), step);
}
return ret;
}

В итоге стандартные библиотеки имеют незадокументированную функцию __resize_default_init, которая просто аллоцирует сырые неинициализированные байты, в которые можно копировать. Abseil это использует для Join строк, а вот в Яндексе делается reserve и добавляется appendом. Тут винить некого, так просто получилось, что интерфейс "выделить сырую память и проинициализировать тем, чем надо" сложно выражется через обычные примитивы строк.

В C++ на этой неделе, надеюсь, примут в C++23 resize_default_init, который исправляет эту проблему. Синтаксис получается функциональный немного, вызывается callback, который должен заполнять байты выделенной строки, чтобы ни в какой момент времени не оставлять строку неинициализированной, возвращаемое значение отвечает за столько байт, сколько останется в строке в итоге.

ret.resize_default_init(step * count, [&](char* buf, size_t n) {
for (size_t i = 0; i < count; i++) {
// GOOD: No bookkeeping
memcpy(buf + i * step, pattern.data(), step);
}
return step * count; // must be <=n, in that case ==n.
});


[1] P1072 пропозал для стандарта
[2] Abseil resize uninitialized
[3] Abseil join
[4] Yandex join
[5] __resize_default_init в LLVM
More from @thisnotes
  1. Sep 25, 2026Спасибо большое!
  2. Sep 25, 2026Post #525
  3. Sep 25, 2026Post #524
  4. Sep 25, 2026Post #523
  5. Sep 25, 2026Post #522
  6. Sep 25, 2026Post #521
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →