Кто-нибудь знает, что может быть хорошей техникой объединения в смысле минимальной задержки создания / уничтожения, низкой конкуренции за блокировку и разумного использования памяти?
Приложение 1.
1.1. Выделение пула объектов / памяти для одного типа обычно не связано с другим типом (исключение см. В разделе 1.3).
1.2. Выделение памяти выполняется одновременно только для одного типа (класса), обычно для нескольких объектов одновременно.
1.3. Если тип объединяет другой тип с помощью указателя (по какой-то причине), эти типы размещаются вместе в одном непрерывном фрагменте памяти.
Приложение 2.
2.1. Известно, что использование коллекции с сериализацией доступа по типу хуже, чем new / delete.
2.2. Приложение используется на разных платформах / компиляторах и не может использовать приемы, специфичные для компилятора / платформы.
Приложение 3.
Становится очевидным, что самая быстрая (с наименьшей задержкой) реализация должна организовать объединение объектов в виде звездообразной сети фабрик. Где центральная фабрика является глобальной для других фабрик, специализирующихся на потоках. Регулярное предоставление / повторное использование объектов более эффективно в фабрике, зависящей от потока, в то время как центральная фабрика может использоваться для балансировки объектов между потоками.
3.1. Как наиболее эффективно организовать связь между центральной фабрикой и фабриками, специализирующимися на нитях?
К сожалению нет. Это определяется логикой приложения.





Если вы еще не смотрели tcmalloc, возможно, вам стоит взглянуть. Основание вашей реализации на ее концепциях может быть хорошим началом. Ключевые моменты:
Дополнительные возможности, которые нельзя сделать с помощью tcmalloc:
Попытайтесь включить локальность ссылки, используя более мелкие пулы распределения. Например, если доступ к нескольким тысячам объектов будет осуществляться вместе, то лучше всего, если они будут расположены близко друг к другу в памяти. (Чтобы свести к минимуму пропуск кэша и сбои TLB.) Если вы выделяете эти экземпляры из их собственного кэша потоков, то они должны иметь достаточно хорошую локализацию.
Если вы заранее знаете, какие экземпляры будут долгоживущими, а какие - нет, то выделите их из отдельных кешей потоков. Если вы не знаете, то периодически копируйте старые экземпляры, используя кэш потоков для выделения, и обновляйте старые ссылки на новые экземпляры.
Чтобы минимизировать задержку при построении / разрушении, вам нужны полностью сконструированные объекты, поэтому вы исключите время создания / ctor / dtor / delete. Эти «бесплатные» объекты можно сохранить в списке, так что вы просто вставляете / нажимаете элемент в конце.
Вы можете заблокировать пулы объектов (по одному для каждого типа) один за другим. Он немного более эффективен, чем общесистемная блокировка, но не имеет накладных расходов, связанных с блокировкой по объектам.
Если ctor и dtor не выделяют или не освобождают память, они должны быть быстрыми, и поэтому не должно быть причин избегать их. Кроме того, проблема с одним списком фрилансеров для каждого типа заключается в том, что вы должны использовать атомарные операции для управления им, а это будет способствовать ложному совместному использованию.
Не уверен, что это лучший ответ. ИМХО тот, что про tcmalloc, был лучше. Но не могу проголосовать
Я предполагаю, что у вас есть профиль, вы измерили свой код после всего этого создания и убедились, что создание / уничтожение на самом деле вызывает проблему. В противном случае это то, что вам следует сделать в первую очередь.
Если вы все еще хотите создать пул объектов, в качестве первого шага вы должны убедиться, что ваши объекты не имеют состояния, потому что это будет предварительным условием для повторного использования объекта. Точно так же вы должны убедиться, что члены объекта и сам объект не имеют проблем с использованием из других потоков, кроме того, который его создал. (Объекты COM STA / оконные ручки и т. д.)
Если вы используете Windows и COM, одним из способов использования пула, предоставляемого системой, будет запись объектов Free Threaded и включение пула объектов, что заставит среду выполнения COM + (ранее известную как MTS) сделать это за вас. Если вы используете какую-то другую платформу, например Java, возможно, вы могли бы использовать серверы приложений, которые определяют интерфейсы, которые должны реализовывать ваши объекты, и сервер COM + мог бы выполнять объединение за вас.
или вы можете накрутить свой собственный код. Но вы должны попытаться найти, есть ли для этого шаблон, и если да, используйте его вместо того, что следует ниже.
Если вам нужно развернуть собственный код, создайте динамически растущую коллекцию, которая отслеживает уже созданные объекты. Предпочтительно использовать вектор для коллекции, так как вы только добавляете в коллекцию, и было бы легко пройти по ней в поисках свободного объекта. (при условии, что вы не удаляете объекты в пуле). Измените тип коллекции в соответствии с вашими политиками удаления (вектор указателей / ссылок на объекты, если вы используете C++, чтобы удалить и воссоздать объект в том же месте)
Каждый объект должен отслеживаться с помощью флага, который может считываться непостоянным образом и изменяться с помощью функции блокировки, чтобы пометить его как используемый / неиспользуемый.
Если используются все объекты, необходимо создать новый объект и добавить его в коллекцию. Перед добавлением вы можете получить блокировку (критический раздел), пометить новый объект как используемый и выйти из блокировки.
Измерьте и продолжайте - возможно, если вы реализовали вышеуказанную коллекцию как класс, вы могли бы легко создавать разные коллекции для разных типов объектов, чтобы уменьшить конкуренцию за блокировку со стороны потоков, выполняющих различную работу.
Наконец, вы могли бы реализовать интерфейс фабрики перегруженных классов, который может создавать все виды объединенных объектов и знает, какая коллекция содержит какой класс.
Затем вы можете оптимизировать этот дизайн.
Надеюсь, это поможет.
Если у вас есть предположение о предпочтительном размере пула, вы можете создать пул фиксированного размера, используя структуру стека с использованием массива (самое быстрое из возможных решений). Затем вам необходимо реализовать четыре фазы: жесткую инициализацию времени жизни объекта (и выделение памяти), мягкую инициализацию, мягкую очистку и жесткую очистку (и освобождение памяти). Теперь в псевдокоде:
Object* ObjectPool::AcquireObject()
{
Object* object = 0;
lock( _stackLock );
if ( _stackIndex )
object = _stack[ --_stackIndex ];
unlock( _stackLock );
if ( !object )
object = HardInit();
SoftInit( object );
}
void ObjectPool::ReleaseObject(Object* object)
{
SoftCleanup( object );
lock( _stackLock );
if ( _stackIndex < _maxSize )
{
object = _stack[ _stackIndex++ ];
unlock( _stackLock );
}
else
{
unlock( _stack );
HardCleanup( object );
}
}
Метод HardInit / HardCleanup выполняет полную инициализацию и уничтожение объекта, и они выполняются только в том случае, если пул пуст или если освобожденный объект не может поместиться в пул, потому что он заполнен. SoftIniti выполняет мягкую инициализацию объектов, она инициализирует только те аспекты объектов, которые могут быть изменены с момента его выпуска. Метод SoftCleanup освобождает ресурсы, используемые объектом, которые должны быть освобождены как можно быстрее, или те ресурсы, которые могут стать недействительными, пока их владелец находится в пуле. Как видите, блокировка минимальна, всего две строки кода (или всего несколько инструкций).
Эти четыре метода могут быть реализованы в отдельных (шаблонных) классах, поэтому вы можете реализовать точно настроенные операции для каждого типа объекта или использования. Также вы можете рассмотреть возможность использования интеллектуальных указателей для автоматического возврата объекта в его пул, когда он больше не нужен.
Проблема в том, что вы увидите много споров за блокировку стека. Кроме того, будет существовать ложное совместное использование, поскольку выделения из отдельных потоков будут смежными. Кроме того, если выделено 3000 временных объектов, а затем выделен 1 постоянный объект, 3000 временных объектов не будут восстановлены.
Я не понимаю последней части, касающейся восстановления временного объекта. Можете быть более конкретными?
Я неправильно прочитал код и неправильно понял ответ; часть «временное отсутствие возврата» была совершенно неправильной. :-) Однако вышеперечисленное можно улучшить, чтобы использовать связанный список, сняв таким образом блокировку. (Вместо этого используйте операции атомарного списка.)
Вы пробовали распределитель запасов? Он обеспечивает лучшую производительность, чем распределитель по умолчанию во многих системах.
Спасибо, было интересно читать. Однако наши ребята сказали мне, что у него есть серьезные проблемы с блокировками по сравнению с umem на Solaris.
Почему у вас есть несколько потоков, уничтожающих объекты, которые они не создавали? Это простой способ управлять временем жизни объекта, но затраты могут сильно варьироваться в зависимости от использования.
В любом случае, если вы еще не начали это реализовывать, по крайней мере, вы можете поместить функцию создания / уничтожения за интерфейс, чтобы вы могли протестировать / изменить / оптимизировать это позже, когда у вас будет больше информации о том, что ваша система действительно делает.
Концепция немного другая. Объект может перемещаться по потокам, обрабатывающим его. И когда его время жизни подошло к концу, оно могло быть в любом другом потоке, кроме материнского потока. Конечно, детали управления загробной жизнью скрыты.
Вы заранее знаете время жизни объекта или он будет передавать потоки?