Техника объединения объектов с минимальной задержкой в ​​многопоточном приложении - PullRequest
1 голос
/ 19 сентября 2008
  1. В приложении имеется около 30 типов объектов, которые создаются неоднократно.
  2. Некоторые из них имеют долгую жизнь (часы), некоторые - короткие (миллисекунды).
  3. Объекты могут быть созданы в одном потоке и уничтожены в другом.

Кто-нибудь знает, что может быть хорошей техникой объединения в смысле минимальной задержки создания / уничтожения, низкой конкуренции за блокировку и разумного использования памяти?

Добавить 1.

1,1. Выделение пула объектов / памяти для одного типа обычно не связано с другим типом (исключение см. В 1.3)

1.2. Выделение памяти выполняется только для одного типа (класса) одновременно, обычно для нескольких объектов одновременно.

1,3. Если тип объединяет другой тип с использованием указателя (по какой-то причине), эти типы размещаются вместе в одном непрерывном фрагменте памяти.

Добавить 2.

2,1. Известно, что использование коллекции с сериализацией доступа по типам хуже, чем new / delete.

2,2. Приложение используется на разных платформах / компиляторах и не может использовать приемы, специфичные для компилятора / платформы.

Приложение 3.

Становится очевидным, что самая быстрая (с наименьшей задержкой) реализация должна организовывать объединение объектов в виде звездообразных сетей предприятий. Где центральный завод является глобальным для других фабрик, ориентированных на конкретные потоки. Регулярное предоставление / переработка объектов более эффективно выполнять в фабрике, ориентированной на потоки, тогда как центральная фабрика может использоваться для балансировки объектов между потоками.

3,1. Каков наиболее эффективный способ организации связи между центральной фабрикой и конкретными фабриками?

Ответы [ 6 ]

4 голосов
/ 19 сентября 2008

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

Если вы все еще хотите выполнить объединение объектов, в качестве первого шага вы должны убедиться, что ваши объекты не содержат состояний, что является необходимым условием для повторного использования объекта. Точно так же вы должны убедиться, что члены объекта и сам объект не имеют проблем с использованием из других потоков, отличных от того, который его создал. (Объекты COM STA / дескрипторы окон и т. Д.)

Если вы используете Windows и COM, одним из способов использования пула, предоставляемого системой, было бы написание свободных потоковых объектов и включение пула объектов, что сделает выполнение COM + (ранее известное как MTS) для вас. Если вы используете какую-то другую платформу, такую ​​как Java, возможно, вы могли бы использовать серверы приложений, которые определяют интерфейсы, которые должны реализовывать ваши объекты, а сервер COM + мог бы сделать пул за вас.

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

Если вам нужно развернуть собственный код, создайте динамически растущую коллекцию, которая отслеживает уже созданные объекты. Используйте вектор предпочтительно для коллекции, так как вы будете только добавлять в коллекцию, и было бы легко обойти его в поисках свободного объекта. (при условии, что вы не удаляете объекты в пуле). Измените тип коллекции в соответствии с вашей политикой удаления (вектор указателей / ссылок на объекты, если вы используете C ++, чтобы удалить и воссоздать объект в том же месте)

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

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

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

Наконец, вы могли бы реализовать перегруженный интерфейс фабрики классов, который может создавать все виды объединенных объектов и знать, какая коллекция содержит какой класс

Вы можете оптимизировать этот дизайн оттуда.

Надеюсь, это поможет.

2 голосов
/ 19 сентября 2008

Чтобы минимизировать задержку построения / уничтожения, вам нужны полностью сконструированные объекты под рукой, поэтому вы исключите новое время / ctor / dtor / delete. Эти «свободные» объекты могут быть сохранены в списке, так что вы просто вставляете / толкаете элемент в конце.

Вы можете заблокировать пулы объектов (по одному для каждого типа) по одному. Это немного более эффективно, чем общесистемная блокировка, но не имеет накладных расходов на блокировку побочных объектов.

1 голос
/ 19 сентября 2008

Если вы еще не посмотрели tcmalloc, вы можете посмотреть. Основой вашей реализации на ее концепциях может быть хорошее начало. Ключевые моменты:

  • Определить набор классов размеров. (Каждое распределение будет выполняться с использованием записи из распределения равного или большего размера.)
  • Используйте один размер-класс на страницу. (Все экземпляры на странице имеют одинаковый размер.)
  • Используйте фрирайлисты для каждого потока, чтобы избежать атомарных операций на каждом alloc / dealloc
  • Если список фрилансов для каждого потока слишком велик, переместите некоторые экземпляры обратно в центральный список фриланса. Попробуйте переместить выделение с той же страницы.
  • Когда список фрилансов для каждого потока пуст, возьмите его из центрального списка фриланса. Попробуйте взять смежные записи.
  • Важно: Вы, вероятно, знаете это, но убедитесь, что ваш дизайн сведет к минимуму ложное распространение.

Дополнительные вещи, которые вы не можете сделать tcmalloc:

  • Попробуйте включить локальность ссылок, используя более детальные пулы распределения. Например, если несколько тысяч объектов будут доступны вместе, то лучше всего, если они находятся близко друг к другу в памяти. (Чтобы минимизировать пропущенный кеш и ошибки TLB.) Если вы выделяете эти экземпляры из их собственного потокового кеша, то они должны иметь довольно хорошую локализацию.

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

0 голосов
/ 19 сентября 2008

Почему у вас есть несколько потоков, уничтожающих объекты, которые они не создали? Это простой способ управлять временем жизни объекта, но его стоимость может сильно различаться в зависимости от использования.

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

0 голосов
/ 19 сентября 2008

Вы пробовали распределитель запаса ? Он обеспечивает лучшую производительность, чем распределитель по умолчанию во многих системах.

0 голосов
/ 19 сентября 2008

Если у вас есть предположение о предпочтительном размере пула, вы можете создать пул фиксированного размера, используя структуру стека с использованием массива (самое быстрое решение). Затем вам нужно реализовать четыре фазы: жесткую инициализацию времени жизни объекта (и распределение памяти), мягкую инициализацию, мягкую очистку и жесткую очистку (и освобождение памяти). Теперь в псевдокоде:

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 освобождает ресурсы, используемые объектом, которые должны быть освобождены как можно быстрее, или те ресурсы, которые могут стать недействительными в течение времени, когда его владелец находится в пуле. Как видите, блокировка минимальна, всего две строки кода (или только несколько инструкций).

Эти четыре метода могут быть реализованы в отдельных (шаблонных) классах, так что вы можете реализовать тонко настроенные операции для типа объекта или использования. Также вы можете рассмотреть возможность использования интеллектуальных указателей для автоматического возврата объекта в его пул, когда он больше не нужен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...