Pull to refresh

Comments 33

+1 к jemalloc. Например, он вполне успешно используется в качестве дефолтного аллокатора в языке Rust, на всех платформах вплоть до Windows XP.
Будет ли это работать, если выделить память из одного потока, а освободить из другого?
Нет конечно. Самая простая доработка — в первых четырех/восьми байтах хранить хэндл кучи и возвращать сдвинутый указатель.
UFO just landed and posted this here
Согласен, идея один поток <-> одна куча не нова (это справедливо не только для MS), вопрос лишь в том, когда это действительно необходимо. В моём случае это оказалось именно так. Насчёт пулов фрагментов памяти — Вам не кажется, что Вы изобретаете велосипед (куча, по сути, как раз и является таким пулом)? Насчёт поисков кусков подходящего размера — LFH куча и так справится с этим на ура. Неоспоримый факт, что усложнение кода такими «оптимизациями» приведёт к дополнительным багам, а вырастет ли быстродействие — вопрос. Насчёт несериализуемой кучи — полезное замечание. Ещё один нюанс — в сумме небольшой и хорошо читаемый исходник моего решения, который реально работает.
UFO just landed and posted this here
В Линукс все растпространённые аллокаторы так и делают — выделяют большие куски прямо с помощью mmap (GNUшный ptmalloc например начиная со 128кБ)
На Windows при выделении фрагмента размером около allocation granularity HeapAlloc почти напрямую переходит в VirtualAlloc (оверхед небольшой). Аllocation granularity обычно равен 64КБ.
кстати, а как живет parallel_allocator с множеством маленьких аллокаций типа 8-32 байта
А у вас контейнеры, использующие аллокатор parallel_allocator, используются строго в одном потоке?
В смысле, что все модифицирующие (не const) методы вызываются исключительно в одном потоке?

Если нет, то у Вас проблемы. Например, resize или какой-нибудь push_* может вызвать перераспределение памяти и если он будет вызван из другого потока, то heapSlot.get() вернет не ту кучу, которую Вы ожидаете.

Если такая проблема возможна, то, как вариант, можно посмотреть в сторону stateful аллокаторов (С++11), но я не уверен, поддерживает ли их STL из VisualStudio (но они работают в boost::container).

В дополнение могу посоветовать посмотреть на tbbmalloc из Intel Threading Building Blocks. Их opensource лицензия позволяет применять библиотеку даже в ПО с закрытым кодом, сейчас у них появились Community лицензии, а на моих задачах (многопоточных с высокой интенсивность перераспределения памяти) использование этого аллокатора сократило нагрузку на CPU на 5-7% по сравнению с tcmalloc (который сейчас живет по адресу: https://github.com/gperftools/gperftools).
Разумеется. Все модификации контейнеров с данным аллокатором проводятся только в потоке, их создавшем. А за советы спасибо.
Я не ставил целью глобальное переопределение new / malloc / delete /free, а в Вашем случае происходит именно это (Ваше «одностроковое решение» в общем случае потребует модификации других библиотек, что не всегда возможно). В случае Intel Threading Building Blocks мне бы идеально подошёл вариант phprus-а.
модификации каких других библиотек? эту строчку нужно добавить _в один_ из модулей, которые будут подгружаться во время загрузки приложения.
Объясняю: Вы переопределяете malloc только в своём модуле (например library.dll) и выделяете память с её помощью. Затем в другом модуле (например, test.exe, в котором она не переопределена) её освобождаете с помощью free (плохой стиль, но тем не менее это возможно). Что произойдёт?
мы переопределяем маллос и в всех студийных рантаймах, приложения (msvcrt*.dll), которые подняты при старте подмены аллокатора. Т.е library.dll и test.exe будут использовать один и тот же аллокатор.
Более того, если используется «плохой стиль» + library.dll и test.exe собраны разными студиями («не повторяйте этого дома»:)), тогда всё равно будет использоваться один подмененный аллокатор.
Хм, интересно. Каким образом достигается такой эффект? Мне приходят на ум приходят 2 варианта: патч msvcrt и патчи IAT в других библиотеках. Вы писали, что
мы переопределяем маллос и в всех студийных рантаймах, приложения (msvcrt*.dll), которые подняты при старте подмены аллокатора
, из чего я склоняюсь ко второму варианту. Что насчёт библиотек, загруженных после старта (LoadLibrary и аналоги)?
Первый вариант («патч msvcrt»). Поскольку у каждой студии свой рантайм (msvcrt70.dll,msvcrt71.dll...,msvcrt120.dll,ucrtbase.dll), я указал «рантаймы» во множественном числе. Они все меняются одновременно. Соответственно, если какая-то новая библиотека, которая загружена позже через LoadLibrary(), использует тот же рантайм, что был уже перегружен при запуске программы, то эта новая библиотека будет использовать перегруженный аллокатор. Если же используется какой-то новый рантайм, в котором аллокатор не перегружен, то при определенных обстоятельствах неправильного использования можно столкнуться со знаменитым «cross module boundaries hell».
Тогда такой вопрос: что происходит в случае статической CRT (ключ /MT в VS)?
этот модуль будет использовать CRT аллокатор с выделенным пулом памяти. Это всё равно, что построить обе library.dll и test.exe со статическим рантаймом и пробовать выделять/освобождать память в разных модулях. Классический «cross module boundaries hell».
Понятно. Лично я обошёл бы подобную технику стороной (к чему злить антималварный софт).
Дискуссия — громко сказано. Назначение scalable_allocator, пожалуй, сходное, хотя вдумчиво прочитать док пока нет возможности. Но есть одна большая разница — код, приведенном в данной статье, может использовать любой желающий совершенно задаром, можно ли то же самое сказать про Intel-овский? В нём нет никаких акцентов на аппаратную платформу, что насчёт Intel-овского (просто предположение)? Ещё такой вопрос: scalable_allocator тоже использует приватные кучи?
1. scalable_allocator идет под лицензией GPLv2 with runtime exception + Commercial. Лицензии использования алгоритма, представленного в топике не указаны.
2. scalable_allocator использует всю мощь tbbmalloc, который работает в бэкэнде, и соответственно работает на всех платформах, куда запортирован tbbmalloc.
Насчёт 1: в пред. комментарии я указал, что мой код может использовать кто угодно, в том числе в комм. приложениях с закрытым исходным кодом (хотя могу указать BSD или MIT лицензию, шутки ради). Насчёт 2:
может быть, «мощь» tbbmalloc и впрямь велика, но насколько часто она действительно нужна? Стоит ли она того, чтобы за неё платить в описанной ситуации (прим: использовал прив. код в комм. софте)? Приведенный код прекрасно себя чувствует при работе в 8 потоков, но это, как мне кажется, не предел. Плюс его легко портировать на другие платформы, поддерживающие работу с приватными кучами и аналогами TLS (хотя передо мной такая задача не стояла). И ещё: вы пропустили вопрос насчёт аппаратных акцентов (хотя про такое обычно и не говорят).
Насчет п.2 спорить бесполезно, ибо холивар. Если есть какие-то конкретные вопросы, можно обсудить.
Но в целом, мой совет про аллокаторы (не ТС, а всем): необходимо попробовать различные аллокаторы (tbbmalloc, TCMalloc, jemalloc, parallel_allocator, hoard, etc.) и использовать тот, который больше всего устраивает устраивает для этого конкретного приложения или ворклода.
Насчёт выбора подходящего — полностью согласен.
> Насчёт 2: может быть, «мощь» tbbmalloc и впрямь велика, но насколько часто она действительно нужна? Стоит ли она того, чтобы за неё платить в описанной ситуации (прим: использовал прив. код в комм. софте)?
За TBB нужно платить только если нужна поддержка.
Наличие runtime exception позволяет использовать opensource версию TBB даже в коммерческих проектах с закрытым кодом.
Да, пожалуй Вы правы, уже нашёл подтверждение.
Sign up to leave a comment.

Articles