关闭

CPP中解决伪共享

标签: cpp
128人阅读 评论(0) 收藏 举报
分类:

在学习LMAX的disruptor(无锁环形消息队列)时,接触到了CPU-cacheline伪共享的概念.

总结:
当使用颗粒度很小的atomic来代替锁时,由于cpu高速缓存是成块的对内存进行预读,从而在更改atomic时,导致其他核心缓存进行无必要的刷新,降低了性能.(本核心修改的atomic同时存在于其他核心的缓存中,而修改之后值需要在核心间进行同步,来保证原子性)

这里引用查询到的博文:伪共享(False Sharing)

缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,一般为32-256个字节。最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。缓存行上的写竞争是运行在SMP系统中并行线程实现可伸缩性最重要的限制因素。有人将伪共享描述成无声的性能杀手,因为从代码中很难看清楚是否会出现伪共享。

为了让可伸缩性与线程数呈线性关系,就必须确保不会有两个线程往同一个变量或缓存行中写。两个线程写同一个变量可以在代码中发现。为了确定互相独立的变量是否共享了同一个缓存行,就需要了解内存布局,或找个工具告诉我们。Intel VTune就是这样一个分析工具。

这里写图片描述

在清楚问题的基本原因后构建代码进行测试:

#include <atomic>
#include <thread>
#include <iostream>
#include <chrono>

using namespace std;

/// The value of 64 bytes is typical for x86/x64 architectures.
const size_t CacheLineSize = 64;

#define LOOP_NUMS 30000000

class nFlag {
public:
    nFlag() {
        flag1_.clear();
        flag2_.clear();
        flag3_.clear();
    }

    atomic_flag flag1_;
    atomic_flag flag2_;
    atomic_flag flag3_;
};

class nPadFlag {
public:
    nPadFlag() {
        flag1_.clear();
        flag2_.clear();
        flag3_.clear();
    }

    uint8_t m_pad1[CacheLineSize - sizeof(atomic_flag)];
    atomic_flag flag1_;
    uint8_t m_pad2[CacheLineSize - sizeof(atomic_flag)];
    atomic_flag flag2_;
    uint8_t m_pad3[CacheLineSize - sizeof(atomic_flag)];
    atomic_flag flag3_;
    uint8_t m_pad4[CacheLineSize - sizeof(atomic_flag)];
};

void f_add(int *num, atomic_flag *flag) {
    for(int i = 0; i < LOOP_NUMS; ++i) {
        while(flag->test_and_set(std::memory_order_acquire));

        *num += 1;

        flag->clear(std::memory_order_release);
    }
}

void main() {
    //int arr[3];
    //memset(arr, 0, sizeof(arr));

    int a = 0, b = 0, c = 0;

    nFlag nor_flag;
    nPadFlag pad_flag;
    {
        auto s_time = chrono::steady_clock::now();
        for(int loop = 0; loop < 20; ++loop) {
            thread th1 = thread(f_add, &a, &(pad_flag.flag1_));
            thread th2 = thread(f_add, &b, &(pad_flag.flag2_));
            thread th3 = thread(f_add, &c, &(pad_flag.flag3_));

            th1.join();
            th2.join();
            th3.join();

            //cout << arr[0] << " " << arr[1] << " " << arr[2] << endl;
        }
        auto t_elapse = chrono::steady_clock::now() - s_time;
        cout << "with pad: " << t_elapse.count() / 1000000 / 20 << endl;
    }
    {
        auto s_time = chrono::steady_clock::now();
        for(int loop = 0; loop < 20; ++loop) {
            thread th1 = thread(f_add, &a, &(nor_flag.flag1_));
            thread th2 = thread(f_add, &b, &(nor_flag.flag2_));
            thread th3 = thread(f_add, &c, &(nor_flag.flag3_));

            th1.join();
            th2.join();
            th3.join();

            //cout << arr[0] << " " << arr[1] << " " << arr[2] << endl;
        }

        auto t_elapse = chrono::steady_clock::now() - s_time;
        cout << "without: " << t_elapse.count() / 1000000 / 20 << endl;
    }
    getchar();
}

在windows10 X64的机器上,使用vs2015编译得到release结果:
with pad: 2024 (ms)
without: 2995 (ms)

规避了false sharing后得在3线程下得到了30%的性能提升

0
0

猜你在找
【直播】机器学习&数据挖掘7周实训--韦玮
【套餐】系统集成项目管理工程师顺利通关--徐朋
【直播】3小时掌握Docker最佳实战-徐西宁
【套餐】机器学习系列套餐(算法+实战)--唐宇迪
【直播】计算机视觉原理及实战--屈教授
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之矩阵--黄博士
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之凸优化--马博士
【套餐】Javascript 设计模式实战--曾亮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:8311次
    • 积分:236
    • 等级:
    • 排名:千里之外
    • 原创:10篇
    • 转载:2篇
    • 译文:0篇
    • 评论:0条
    文章分类