C++性能榨汁机之伪共享

来源  http://irootlee.com/juicer_false_sharing/

前言

  在多核并发编程中,如果将互斥锁的争用比作“性能杀手”的话,那么伪共享则相当于“性能刺客”。“杀手”与“刺客”的区别在于杀手是可见的,遇到杀手时我们可以选择战斗、逃跑、绕路、求饶等多种手段去应付,但“刺客”却不同,“刺客”永远隐藏在暗处,伺机给你致命一击,防不胜防。具体到我们的并发编程中,遇到锁争用影响并发性能情况时,我们可以采取多种措施(如缩短临界区,原子操作等等)去提高程序性能,但是伪共享却是我们从所写代码中看不出任何蛛丝马迹的,发现不了问题也就无法解决问题,从而导致伪共享在“暗处”严重拖累程序的并发性能,但我们却束手无策。

缓存行

  为了进行下面的讨论,我们需要首先熟悉缓存行的概念,学过操作系统课程存储结构这部分内容的同学应该对存储器层次结构的金字塔模型印象深刻,金字塔从上往下代表存储介质的成本降低、容量变大,从下往上则代表存取速度的提高。位于金字塔模型最上层的是CPU中的寄存器,其次是CPU缓存(L1,L2,L3),再往下是内存,最底层是磁盘,操作系统采用这种存储层次模型主要是为了解决CPU的高速与内存磁盘低速之间的矛盾,CPU将最近使用的数据预先读取到Cache中,下次再访问同样数据的时候,可以直接从速度比较快的CPU缓存中读取,避免从内存或磁盘读取拖慢整体速度。

  CPU缓存的最小单位就是缓存行,缓存行大小依据架构不同有不同大小,最常见的有64Byte和32Byte,CPU缓存从内存取数据时以缓存行为单位进行,每一次都取需要读取数据所在的整个缓存行,即使相邻的数据没有被用到也会被缓存到CPU缓存中(这里又涉及到局部性原理,后面文章会进行介绍)。

缓存一致性

  在单核CPU情况下,上述方法可以正常工作,可以确保缓存到CPU缓存中的数据永远是“干净”的,因为不会有其他CPU去更改内存中的数据,但是在多核CPU下,情况就变得更加复杂一些。多CPU中,每个CPU都有自己的私有缓存(可能共享L3缓存),当一个CPU1对Cache中缓存数据进行操作时,如果CPU2在此之前更改了该数据,则CPU1中的数据就不再是“干净”的,即应该是失效数据,缓存一致性就是为了保证多CPU之间的缓存一致。

  Linux系统中采用MESI协议处理缓存一致性,所谓MESI即是指CPU缓存的四种状态:

  • M(修改,Modified):本地处理器已经修改缓存行,即是脏行,它的内容与内存中的内容不一样,并且此 cache 只有本地一个拷贝(专有);
  • E(专有,Exclusive):缓存行内容和内存中的一样,而且其它处理器都没有这行数据;
  • S(共享,Shared):缓存行内容和内存中的一样, 有可能其它处理器也存在此缓存行的拷贝;
  • I(无效,Invalid):缓存行失效, 不能使用。

  每个CPU缓存行都在四个状态之间互相转换,以此决定CPU缓存是否失效,比如CPU1对一个缓存行执行了写入操作,则此操作会导致其他CPU的该缓存行进入Invalid无效状态,CPU需要使用该缓存行的时候需要从内存中重新读取。由此就解决了多CPU之间的缓存一致性问题。

伪共享

  何谓伪共享?上面我们提过CPU的缓存是以缓存行为单位进行的,即除了本身所需读写的数据之外还会缓存与该数据在同一缓存行的数据,假设缓存行大小是32字节,内存中有“abcdefgh”八个int型数据,当CPU读取“d”这个数据时,CPU会将“abcdefgh”八个int数据组成一个缓存行加入到CPU缓存中。假设计算机有两个CPU:CPU1和CPU2,CPU1只对“a”这个数据进行频繁读写,CPU2只对“b”这个数据进行频繁读写,按理说这两个CPU读写数据没有任何关联,也就不会产生任何竞争,不会有性能问题,但是由于CPU缓存是以缓存行为单位进行存取的,也是以缓存行为单位失效的,即使CPU1只更改了缓存行中“a”数据,也会导致CPU2中该缓存行完全失效,同理,CPU2对“b”的改动也会导致CPU1中该缓存行失效,由此引发了该缓存行在两个CPU之间“乒乓”,缓存行频繁失效,最终导致程序性能下降,这就是伪共享。

如何避免伪共享

  避免伪共享主要有以下两种方式:

  1. 缓存行填充(Padding):为了避免伪共享就需要将可能造成伪共享的多个变量处于不同的缓存行中,可以采用在变量后面填充字节的方式达到该目的。
  2. 使用某些语言或编译器中强制变量对齐,将变量都对齐到缓存行大小,避免伪共享发生。

总结

  一般伪共享都很隐蔽,很难被发现,当伪共享真正构成性能瓶颈的时候,我们有必要去努力找到并解决它,但是在大部分对性能追求没有那么高的应用中,伪共享的存在对程序的危害很小,有时并不值得耗费精力和额外的内存空间(缓存行填充)去查找系统存在的伪共享。还是那句我一直以来遵循的话“不要过度优化,不要提前优化。”。

==================== End

C++性能榨汁机之伪共享的更多相关文章

  1. 伪共享(false sharing),并发编程无声的性能杀手

    在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...

  2. 并发性能的隐形杀手之伪共享(false sharing)

    在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...

  3. 多线程中的volatile和伪共享

      伪共享 false sharing,顾名思义,“伪共享”就是“其实不是共享”.那什么是“共享”?多CPU同时访问同一块内存区域就是“共享”,就会产生冲突,需要控制协议来协调访问.会引起“共享”的最 ...

  4. 伪共享和缓存行填充,从Java 6, Java 7 到Java 8

    关于伪共享的文章已经很多了,对于多线程编程来说,特别是多线程处理列表和数组的时候,要非常注意伪共享的问题.否则不仅无法发挥多线程的优势,还可能比单线程性能还差.随着JAVA版本的更新,再各个版本上减少 ...

  5. java中伪共享问题

    伪共享(False Sharing) 原文地址:http://ifeve.com/false-sharing/ 作者:Martin Thompson  译者:丁一 缓存系统中是以缓存行(cache l ...

  6. 关于java中的伪共享的认识和解决

    在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素: CPU缓存 网页浏览器为了加快速度,会在本机存缓存以前浏览过 ...

  7. Java 中的伪共享详解及解决方案

    1. 什么是伪共享 CPU 缓存系统中是以缓存行(cache line)为单位存储的.目前主流的 CPU Cache 的 Cache Line 大小都是 64 Bytes.在多线程情况下,如果需要修改 ...

  8. 伪共享(False Sharing)

    原文地址:http://ifeve.com/false-sharing/ 作者:Martin Thompson  译者:丁一 缓存系统中是以缓存行(cache line)为单位存储的.缓存行是2的整数 ...

  9. 杂谈 什么是伪共享(false sharing)?

    问题 (1)什么是 CPU 缓存行? (2)什么是内存屏障? (3)什么是伪共享? (4)如何避免伪共享? CPU缓存架构 CPU 是计算机的心脏,所有运算和程序最终都要由它来执行. 主内存(RAM) ...

随机推荐

  1. Flutter移动电商实战 --(16)切换后页面状态的保持AutomaticKeepAliveClientMixin

    底栏切换每次都重新请求是一件非常恶心的事,flutter 中提供了AutomaticKeepAliveClientMixin 帮我们完成页面状态保存效果. 1.AutomaticKeepAliveCl ...

  2. react native Expo适配全面屏/Expo识别全面屏和正常屏

    一.最新版本的expo已经默认支持了全面屏,即不会像react native cli一样出现底部黑边 二.但是全面屏通过Dimensions.get('window')获取的高度还是不准确,因为全面屏 ...

  3. JavaFX入门:简单Demo-学习NetBeans开发平台

    零. 最终目标 通过两种方式(纯代码控制.FXML),实现一个简单的登录界面:   Paste_Image.png 涉及到的控件: 文本(Text,动态显示内容).标签(Label,显示文本).文本域 ...

  4. scikit-learn机器学习(四)使用决策树做分类,并画出决策树,随机森林对比

    数据来自 UCI 数据集 匹马印第安人糖尿病数据集 载入数据 # -*- coding: utf-8 -*- import pandas as pd import matplotlib matplot ...

  5. 独立的js文件中不能使用EL表达式取值

    在独立的js文件中写了一个EL表达式取值,发现没有取到值,原因在于不能在独立的js文件中使用EL表达式,可以在jsp页面定义全局变量,然后在js文件中引用

  6. ThreadLocal的简单介绍

    ThreadLocal是什么 早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路.使用这个工具类可以很简洁地 ...

  7. Core Data概述(转)

    Core Data是一个模型层的技术.Core Data帮助你建立代表程序状态的模型层.Core Data也是一种持久化技术,它能将模型对象的状态持久化到磁盘,但它最重要的特点是:Core Data不 ...

  8. uWSGI 漏洞复现(CVE-2018-7490)

    uWSGI是一个Web服务器,它实现了WSGI协议.uwsgi.http等协议.Nginx中HttpUwsgiModule的作用是与uWSGI服务器进行交换.WSGI是一种Web服务器网关接口.它是一 ...

  9. std::replace函数

    需包含头文件#include <algorithm> template <class ForwardIterator, class T> void replace (Forwa ...

  10. 最新 波克城市java校招面经 (含整理过的面试题大全)

    从6月到10月,经过4个月努力和坚持,自己有幸拿到了网易雷火.京东.去哪儿.波克城市等10家互联网公司的校招Offer,因为某些自身原因最终选择了波克城市.6.7月主要是做系统复习.项目复盘.Leet ...