Когда вы смотрите на «размер издержек» для любого типа примитива синхронизации, имейте в виду, что эти не могут быть упакованы слишком близко. Это так, например, потому что два мьютекса, совместно использующие линию кэширования, в конечном итоге могут оказаться в кэш-памяти (ложное разделение), если они используются одновременно, даже если пользователи, получающие эти блокировки, никогда не «конфликтуют». То есть представьте два потока, выполняющих два цикла:
for (;;) {
lock(lockA);
unlock(lockA);
}
и
for (;;) {
lock(lockB);
unlock(lockB);
}
Вы увидите удвоенное число итераций при запуске в двух разных потоках по сравнению с одним потоком, выполняющим один цикл , если и только если , то две блокировки не находятся в пределах одной и той же кэш-линии . Если lockA
и lockB
находятся в одной и той же кэшированной строке, число итераций на поток уменьшится вдвое, потому что линия кэширования с этими двумя блокировками будет постоянно перебрасываться между ядрами процессора, выполняющими эти два потока.
Следовательно, даже если фактический размер данных примитивного типа данных, лежащий в основе спин-блокировки или мьютекса, может быть только байтом или 32-битным словом, эффективный размер данных такого объекта часто больше.
Имейте это в виду, прежде чем утверждать "мои мьютексы слишком велики". На самом деле, в x86 / x64 40 байтов слишком малы , чтобы предотвратить ложное совместное использование, так как в настоящее время в кэше есть не менее 64 байтов.
Кроме того, если вы сильно обеспокоены использованием памяти, учтите, что объекты уведомлений не обязательно должны быть уникальными - переменные условия могут служить для запуска различных событий (через predicate
, о котором знает boost::condition_variable
). Поэтому было бы возможно использовать одну пару мьютекс / CV для всего конечного автомата вместо одной такой пары на состояние. То же самое касается, например, синхронизация пула потоков - иметь больше блокировок, чем потоков, не обязательно выгодно.
Редактировать: Еще несколько ссылок на «ложное совместное использование» (и отрицательное влияние на производительность, вызванное размещением нескольких атомарно-обновляемых переменных в одной и той же кэшированной строке), см. (Среди прочих) следующие публикации SO :
Как уже говорилось, при использовании нескольких «объектов синхронизации» (будь то атомно-обновляемые переменные, блокировки, семафоры ...) в многоядерной конфигурации кэша на ядро, разрешите каждому из них отдельная кешлайн пространства. Вы торгуете использованием памяти для масштабируемости здесь, но на самом деле, если вы попадаете в регион, где вашему программному обеспечению требуется несколько миллионов блокировок (то есть в ГБ памяти), у вас либо есть финансирование на несколько сотен ГБ памяти (и сотен ядер ЦП), или вы что-то не так делаете в дизайне своего программного обеспечения.
В большинстве случаев (блокировка / атом для конкретного экземпляра class
/ struct
) вы получаете «заполнение» бесплатно, если экземпляр объекта, который содержит атомарную переменную, достаточно велик.