Pull to refresh

Comments 11

Вы бы репозиторий с примером открыли, а то ссылка есть, а репозиторий не доступен

Сорри, очистка от тонны экспериментов заняла чуть больше времени, чем ожидалось.
Репы открыты, велкам.

return (top_ = *static_cast<void**>(node)), node;
 (*static_cast<void**>(node) = top_), top_ = node;
return (s_cursor = c1), c;

Не хотелось бы мне сопровождать код написанный в таком стиле.

Привет )

Это классический паттерн однонаправленного списка в аллокаторах через void**.

Не всегда под единственное первое слово блока памяти декларируют аж тип, тем более, что это тоже не совсем корректно, делать простой reinterpret_cast<> к некоему BlockHeader - формально это UB, поэтому уровень "почти асма" именно в этой точке не напрягает (приведение к void** автоматически отсекает рассуждения насчёт "подержки прикладными программистами").

Если уж включать пуризм, то требуется обкладывать через start_lifetime_as из С++23, но я не был уверен, во что это выльется, а тут заведомо знаю во что - паттерн типовой.

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

Я не про работу с void*, а про использование оператора запятая для минимизации количества строк кода.

Вся эти тема возникла как фан, где каждый момент будет решен или по-максимому, или никак. ))

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

Спасибо, что обратил внимание, стоило чуть сфокусироваться и там можно элегантно без reinterpret_cast<>, UB или попыток его избежать через start_lifetime_as, но при этом получаем те же самые две операции в одной строке ))

s_top = ::new (mem) free_block{s_top};

В bump_alloc тоже отформатировал код, после всех причёсываний бинарник совпал до байта, ну и ОК.

Размеры пулов по степеням двойки – это, возможно, хорошо для скорости. Но при большом количестве одновременно живущих небольших объектов такая политика ведет к заметному перерасходу памяти.

Хороший вопрос.
Наверно стоило отразить в статье, почему именно по степеням двойки.

Частично уже отражено - упомянуто про "морской прибой", когда сцена описывается большим С++ выражением, элементы которого массово создаются в рамках выражения и умирают в конце вычисления этого выражения.

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

Второй популярный сценарий - это строки и прочие контейнеры (вектор, буфера для библиотеки fmt, просто байт-буфера для I/O), где размеры выделенных блоков памяти всё еще вменяемого размера, т.е. которые всё еще имеет смысл выделять через кастомные аллокаторы. В этих сценариях даже без использования пул-аллокаторов приращение памяти обычно происходит вдвое при росте размера буфера - звёзды сошлись и тут. Т.е., перерасход по степеням двойки вложен в эти контейнеры изначально их механикой grow (хотя с этим и можно бороться через точный reserve).

И да, как орудовать памятью, избегая степенного перерасхода, будет рассказано в статье о парсере XML - там личная арена на каждый XML-документ поверх общего для всех XML-документов аллокатора блоков, из которых состоят их личные арены. Но посмотреть на это всё можно уже прямо сейчас по ссылкам на зонтичный проект, подпроект wxl.xml.

Хороший вопрос.

Это как бы не вопрос, а частный опыт. Описанные сценарии относятся к короткоживущим объектам, поэтому, может быть, на перерасход не обращается внимание. Ну типа есть всплески, ну и что.

А вот когда создается 100500 мелких объектов, которые потом живут часами, то можно обнаружить, что где-то 20%, где-то 30%, а где-то и больше, занятой памяти – это как раз пустое место в выделенных блоках. Когда создается объект размером в 36 байт, а попадает он уже в пул для 64-байтовых блоков.

Прямо читаешь мои мысли. ))

Всё так. Поэтому долгоживущие данные (например, разметка книги для читалки) живут в Арене, где для utf-8 строк выравнивание 1 байт, а для UTF-16, соответственно, 2 байта.

И как раз в отдельном worktree неспешно экспериментирую с вариантом сразу двух арен для долгоживующих XML-документов, потому что прямо сейчас из одной арены выделяются как строки, так и узлы самого XML-документа, где выравнивание узла происходит по машинному слову, а длины строк, сохраняемых в той же самой арене - произвольные.

Итого:
1. Происходят микропотери памяти (1-7 байт для x64 с матожиданием в 4 байта на каждый элемент) из-за смешанного расположения элементов с различным выравниванием.
2. Происходят дополнительные вычисления для целей выравнивания текущего курсора арены (2-3 лишних инструкции).

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

А неспешность вызвана тем, что есть еще варианты - это попробовать так организовать разбор, чтобы пытаться класть в арену узлы с узлами, а строки со строками, уменьшая среднее матожидание размера выкинутых на выравнивании "хвостиков", но каждое изменение заметно отражается на производительности, а основным критерием оптимизации является голая производительность, т.е. отзывчивость читалки, поэтому парсер и парсит в районе полугигабайта XML в секунду, и работа над ним на данном этапе натурально хирургически-ювелирная... Впрочем, как и над всем остальным в проекте. ))

Раз в жизни хочется уже "отвести душу", достигнув теоретического экстремума натурально во всех аспектах целевой задачи.

Sign up to leave a comment.

Articles