Проблема фальшивого спільного використання

Фальшиве спільне використання (False sharing) є критичною проблемою в високопродуктивних багатопотокових програмах на C++. Вона виникає, коли два або більше потоки працюють з різними змінними, які знаходяться на одній і тій самій лінії кешу. Сучасні процесори оптимізують продуктивність за допомогою кешів, які зберігають блоки пам'яті (лінії кешу) близько до процесора. Зазвичай лінія кешу має розмір 64 байти. Якщо два потоки змінюють різні змінні в межах однієї лінії кешу, це може призвести до зайвих проблем із узгодженістю кешу, які вирішуються апаратно через протокол узгодженості кешу (наприклад, MESI), але це впливає на продуктивність.

pic

Після того як Core 1 змінює gVar1, відповідна лінія кешу в Core 2 буде позначена як недійсна, оскільки вона залежить від зміни, зробленої в Core 1. Тому Core 2 зазнає промаху кешу і повинен отримати лінію кешу з Core 1 або з основної пам'яті, що призводить до зайвої затримки.

Як вирішити цю проблему?

Вирівнювання даних

Вирівнюючи спільні дані на розмір лінії кешу, ви запобігаєте тому, щоб дві змінні потрапляли в одну й ту саму лінію кешу, тим самим усуваючи ризик "фальшивого спільного використання". Починаючи з C++11, специфікатор alignas дозволяє вирівнювати пам'ять до конкретних розмірів.

struct alignas(64) Data  
{  
 int32_t member;  
 ...  
};

Зниження продуктивності

Приклад використання: кілька потоків підсумовують всі елементи масиву з 1 мільйоном записів. Кожен потік постійно записує проміжні значення у свій акумулятор (спільний вектор для всіх потоків). Різниця між наявністю "фальшивого спільного використання" в цій програмі та його усуненням може перевищувати 16 разів, що сильно залежить від розміру вхідних даних.

pic

Якщо ви хочете експериментувати самостійно, нижче ви можете знайти весь вихідний код.
Єдина зовнішня залежність — це Google benchmark.

#include   
#include   
#include   
#include   
#include   

namespace WorkerNamespace  
{  

enum class CHECK_OPTION  
{  
 NO_CHECK,  
 CHECK_RESULT  
};  

struct alignas(64) AlignedInt32 {  
 int32_t value;  

 AlignedInt32& operator+=(int32_t rhs) {  
 value += rhs;  
 return *this;  
 }  

 bool operator!=(int32_t rhs) const {  
 return value != rhs;  
 }  
};  

std::ostream& operator<<(std::ostream& os, const AlignedInt32& obj) {  
 os << obj.value;  
 return os;  
}  

template  
class Worker  
{  

public:  

 Worker() : lMaxNoThreads{std::thread::hardware_concurrency()}, lAcc(lMaxNoThreads, T{0}) {}  

 template  
 void work(const std::array& aData)  
 {   
 std::vector lThreads;  
 lThreads.reserve(lMaxNoThreads);   

 for(int32_t i{0}; i lAcc;  

}; // class Worker  
} // namespace WorkerNamespace  

template  
static void BM_Sharing(benchmark::State& state)   
{  
 std::array lInData;  
 std::iota(lInData.begin(), lInData.end(), 0);  

 for (auto _ : state)   
 {  
 T lWorker;  
 lWorker.work(lInData);  
 }  
}  

BENCHMARK_TEMPLATE(BM_Sharing, WorkerNamespace::Worker)->UseRealTime()->Unit(benchmark::kMillisecond);  
BENCHMARK_TEMPLATE(BM_Sharing, WorkerNamespace::Worker)->UseRealTime()->Unit(benchmark::kMillisecond);  

BENCHMARK_MAIN();




Перекладено з: [False sharing problem](https://medium.com/@sireanu.roland/false-sharing-problem-56d9f4507a5d)

Leave a Reply

Your email address will not be published. Required fields are marked *