Ложное совместное использование лучше всего обнаруживается, когда 2 атомарных переменных в одной и той же строке кэша увеличиваются на разные потоки с помощью операции чтения-изменения-записи (RMW).
Для этого каждый процессор должен очистить буфер хранилища и заблокировать строку кэша на время операции приращения, то есть:
- блокировка строки кэша
- чтение значения из кэша L1 в регистр
- значение приращения внутри регистра
- обратная запись в кэш L1
- разблокировка строки кэша
Эффект одной строки кэша, постоянно пересекающейся между процессорами, заметен даже при полной оптимизации компилятора.
Принудительное расположение обеих переменных в разных строках кэша (путем добавления данных заполнения) может привести к значительному увеличению производительности, поскольку каждый ЦП будет иметь полный доступ к своей собственной строке кэша.
Блокировка строки кэша по-прежнему необходима, но при получении доступа на чтение и запись к строке кэша не теряется время.
Если обе переменные являются простыми целыми числами, ситуация отличается, потому что увеличение целого числа включает простую загрузку и сохранение (т. Е. Не атомарную операцию RMW).
Без дополнения эффекты отскока строк кэша между ядрами все еще могут быть заметны, но в гораздо меньшем масштабе, поскольку блокировка строк кэша больше не используется.
Если вы компилируете с полной оптимизацией, весь цикл while, вероятно, будет заменен одним шагом, и больше не будет никакой разницы.
На моем 4-ядерном X86 я получаю следующие цифры:
atomic int, no padding, no optimization: real 57.960s, user 114.495s
atomic int, padding, no optimization: real 10.514s, user 20.793s
atomic int, no padding, full optimization: real 55.732s, user 110.178s
atomic int, padding, full optimization: real 8.712s, user 17.214s
int, no padding, no optimization: real 2.206s, user 4.348s
int, padding, no optimization: real 1.951s, user 3.853s
int, no padding, full optimization: real 0.002s, user 0.000s
int, padding, full optimization: real 0.002s, user 0.000s