Фальшиве спільне використання (False sharing) є критичною проблемою в високопродуктивних багатопотокових програмах на C++. Вона виникає, коли два або більше потоки працюють з різними змінними, які знаходяться на одній і тій самій лінії кешу. Сучасні процесори оптимізують продуктивність за допомогою кешів, які зберігають блоки пам'яті (лінії кешу) близько до процесора. Зазвичай лінія кешу має розмір 64 байти. Якщо два потоки змінюють різні змінні в межах однієї лінії кешу, це може призвести до зайвих проблем із узгодженістю кешу, які вирішуються апаратно через протокол узгодженості кешу (наприклад, MESI), але це впливає на продуктивність.
Після того як Core 1
змінює gVar1
, відповідна лінія кешу в Core 2
буде позначена як недійсна
, оскільки вона залежить від зміни, зробленої в Core 1
. Тому Core 2
зазнає промаху кешу і повинен отримати лінію кешу з Core 1
або з основної пам'яті, що призводить до зайвої затримки.
Як вирішити цю проблему?
Вирівнювання даних
Вирівнюючи спільні дані на розмір лінії кешу, ви запобігаєте тому, щоб дві змінні потрапляли в одну й ту саму лінію кешу, тим самим усуваючи ризик "фальшивого спільного використання". Починаючи з C++11, специфікатор alignas
дозволяє вирівнювати пам'ять до конкретних розмірів.
struct alignas(64) Data
{
int32_t member;
...
};
Зниження продуктивності
Приклад використання: кілька потоків підсумовують всі елементи масиву з 1 мільйоном записів. Кожен потік постійно записує проміжні значення у свій акумулятор (спільний вектор для всіх потоків). Різниця між наявністю "фальшивого спільного використання" в цій програмі та його усуненням може перевищувати 16 разів, що сильно залежить від розміру вхідних даних.
Якщо ви хочете експериментувати самостійно, нижче ви можете знайти весь вихідний код.
Єдина зовнішня залежність — це 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)