CPP中解决伪共享

原创 2015年11月20日 09:56:53

在学习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%的性能提升

版权声明:本文为博主原创文章,未经博主允许不得转载。

Java Cache Line 伪共享及解决方案

伪共享问题本质:多个变量被cpu加载在同一个缓存行中,当在多线程环境下,多个变量被不同的cpu执行,导致缓存行失效而引起的大量的缓存命中率降低。缓存一致性协议MESI协议中,每个CPU的Cache控制...
  • xx_yTm
  • xx_yTm
  • 2016年11月15日 17:42
  • 908

什么是多线程环境下的伪共享(false sharing)?

什么是多线程环境下的伪共享(false sharing)? 伪共享是多线程系统(每个处理器有自己的局部缓存)中一个众所周知的性能问题。伪共享发生在不同处理器上的线程对变量的修改依赖于相同的缓存行,如下...
  • qq_32575047
  • qq_32575047
  • 2017年11月02日 10:05
  • 262

从Java视角理解CPU缓存和伪共享

转载自:http://ifeve.com/from-javaeye-cpu-cache/               http://ifeve.com/from-javaeye-false-shari...
  • zero__007
  • zero__007
  • 2017年01月05日 10:14
  • 1002

内存伪共享(False Sharing)

博主注:在考虑优化多线程并发的内存使用场景时, 由于CPU缓存机制不尽相同, 建议至少确保有128字节距离, 一般通过设置不使用哑元(dummy)或者跨区分配来避免命中同一缓存行, 以减少不同处理器由...
  • rrrfff
  • rrrfff
  • 2015年04月11日 10:24
  • 16470

伪共享---cache line 填充

我们经常提到一个短语Mechanical Sympathy,这个短语也是Martin博客的标题(译注:Martin Thompson),Mechanical Sympathy讲的是底层硬件是如何运作的...
  • kappen123
  • kappen123
  • 2013年10月25日 15:53
  • 1123

伪共享(false share)简介

现代计算机都带有很多缓存用于解决CPU极快的计算速度和主存duxie
  • WinWill2012
  • WinWill2012
  • 2014年10月17日 20:49
  • 1017

从缓存行出发理解volatile变量、伪共享False sharing、disruptor

volatile关键字 当变量被某个线程A修改值之后,其它线程比如B若读取此变量的话,立刻可以看到原来线程A修改后的值   注:普通变量与volatile变量的区别是volatile...
  • opensure
  • opensure
  • 2015年06月28日 09:37
  • 3530

java 多线程读取数据注意伪共享

伪共享(False Sharing) 原文地址:http://ifeve.com/false-sharing/ http://ifeve.com/falsesharing/ 作者:Ma...
  • aaashen
  • aaashen
  • 2015年04月24日 19:44
  • 1121

在分布式环境中如何解决session共享问题

一、什么是session 二、产生session不一致原因 三、解决方案
  • tanjie_123
  • tanjie_123
  • 2016年11月03日 11:49
  • 1822

局域网共享问题全方位解决

声明:这不是共享组建教程,而是问题解决。如果你对共享一直搞不清,那么你可以花一至几小时的时间来看这篇文章,我相信以后共享问题你基本上都能解决。    看过了很多人写共享教程,看过了更多人写共享问题解...
  • xiaotianlan
  • xiaotianlan
  • 2016年08月23日 09:09
  • 517
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:CPP中解决伪共享
举报原因:
原因补充:

(最多只允许输入30个字)