#include <iostream>
#include <future>
#include <chrono>
using namespace std;
using namespace std::chrono;
int a = 0;
int padding[16]; // avoid false sharing
int b = 0;
promise<void> p;
shared_future<void> sf = p.get_future().share();
void func(shared_future<void> sf, int &data)
{
sf.get();
auto t1 = steady_clock::now();
while (data < 1'000'000'000)
++data;
auto t2 = steady_clock::now();
cout << duration<double, ratio<1, 1>>(t2 - t1).count() << endl;
}
int main()
{
thread th1(func, sf, ref(a)), th2(func, sf, ref(b));
p.set_value();
th1.join();
th2.join();
return 0;
}
Я использую приведенный выше код, чтобы продемонстрировать влияние ложного обмена на производительность. Но, к моему удивлению, заполнение, по-видимому, совсем не ускоряет программу. Интересно, если оба a
а также b
являются атомными переменными, есть очевидное улучшение. Какая разница?
Ложное разделение лучше всего обнаруживается, когда 2 атомные переменные в так же Строка кэша увеличивается различными потоками с помощью операции чтения-изменения-записи (RMW).
Для этого каждый процессор должен очистить буфер хранилища и заблокировать строку кэша на время операции приращения, то есть:
Эффект одной строки кэша, постоянно пересекающейся между процессорами, заметен даже при полной оптимизации компилятора.
Принудительное расположение обеих переменных в разных строках кэша (путем добавления данных заполнения) может привести к значительному увеличению производительности, поскольку каждый ЦП будет иметь полный доступ к своей собственной строке кэша.
Блокировка строки кэша по-прежнему необходима, но при получении доступа на чтение и запись к строке кэша не тратится время.
Если обе переменные являются простыми целыми числами, ситуация отличается, потому что увеличение целого числа включает простую загрузку & хранить (т.е. не атомарную операцию 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
Других решений пока нет …