Code:

public class Main {
static long[][] arr; public static void main(String[] args) {
arr = new long[1024 * 1024][8];
// 横向遍历
long marked = System.currentTimeMillis();
for (int i = 0; i < 1024 * 1024; i += 1) {
for (int j = 0; j < 8; j++) {
sum += arr[i][j];
}
}
System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms"); marked = System.currentTimeMillis();
// 纵向遍历
for (int i = 0; i < 8; i += 1) {
for (int j = 0; j < 1024 * 1024; j++) {
sum += arr[j][i];
}
}
System.out.println("Loop times:" + (System.currentTimeMillis() - marked) + "ms");
}
}

会发现横向遍历时间远远小于纵向遍历

物理结构:

二代i5处理器:

  • CacheLine size:64 Byte //缓存行
  • L1 Data Cache:32KB //L1数据缓存
  • L1 Instruction Cache:32KB //L1指令缓存
  • L2 Cache:256KB
  • L3 Cache:4MB

Martin和Mike的QCon演讲演讲中给出了一些缓存未命中的消耗数据,也就是从CPU访问不同层数数据的时间概念:

从CPU到 大约需要的CPU时钟周期 大约需要的时间
主存 约60-80ns
QPI总线传输(套接字之间,未绘制) 约20ns的
L3缓存 约40-45周期 约15ns的
L2缓存 约10个周期 约3ns的
L1缓存 约3-4个周期 约1ns的
寄存器 1个周期

CPU <--- > 寄存器<--- > 缓存<--- >内存

可见CPU读取主存中的数据会比从L1中读取慢了近2个数量级。

L3是每个处理器里面核心共享的,处理器之间不共享

主存是多个处理器共享的

缓存行Cache Line

缓存行(Cache Line) 便是CPU Cache 中的最小单位,CPU Cache 由若干缓存行组成,一个缓存行的大小通常是64 字节(这取决于CPU),并且它有效地引用主内存中的一块地址。一个Java 的long 类型是8 字节,因此在一个缓存行中可以存8 个long 类型的变量。

试想一下你正在遍历一个长度为16 的long 数组data[16],每一个long类型为8字节,原始数据自然存在于主内存中,访问过程描述如下

  • 访问data[0],CPU core 尝试访问CPU Cache,未命中。
  • 尝试访问主内存,操作系统一次访问的单位是一个Cache Line 的大小— 64 字节,这意味着:既从主内存中获取到了data[0] 的值,同时将data[0] ~ data[7 ] 加入到了CPU Cache 之中,for free~
  • 访问data[1]~data[7],CPU core 尝试访问CPU Cache,命中直接返回。
  • 访问data[8],CPU core 尝试访问CPU Cache,未命中。
  • 尝试访问主内存。重复步骤 2

CPU 缓存在顺序访问连续内存数据时挥发出了最大的优势

伪共享:

概念:

通常提到缓存行,大多数文章都会提到伪共享问题(正如提到CAS 便会提到ABA 问题一般)。

伪共享指的是多个线程同时读写同一个缓存行的不同变量时导致的CPU 缓存失效。尽管这些变量之间没有任何关系,但由于在主内存中邻近,存在于同一个缓存行之中,它们的相互覆盖会导致频繁的缓存未命中,引发性能下降。伪共享问题难以被定位,如果系统设计者不理解CPU 缓存架构,甚至永远无法发现— 原来我的进程还可以更快。

果多个线程的变量共享了同一个CacheLine,任意一方的修改操作都会使得整个CacheLine 失效(因为CacheLine 是CPU 缓存的最小单位),也就意味着,频繁的多线程操作, CPU 缓存将会彻底失效,降级为CPU core 和主内存的直接交互

解决办法:

处理伪共享的两种方式:

  1. 字节填充:增大元素的间隔,使得不同线程访问的元素位于不同的缓存线上,典型的空间换时间。
  2. 在每个线程中创建对应元素的本地拷贝,结束后再写回全局数组,相当于线程各自操作自己的数据

字节填充:

Java8中:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.TYPE})
public @interface Contended {
String value() default "";
}

Java8 中提供了字节填充的官方实现,这使得CPU Cache 更加可控了,无需担心jdk 的无效字段优化,无需担心Cache Line 在不同CPU 下的大小究竟是不是64 字节。使用@Contended 注解可以完美的避免伪共享问题。

但是这个功能暂时还是实验性功能,暂时还没到默认普及给用户代码用的程度。要在用户代码(非引导类加载器或扩展类加载器所加载的类)中使用@Contended注解的话,需要使用 - XX:-RestrictContended参数。

比如在JDK 8的ConcurrentHashMap源码中,使用 [@sun.misc.Contended]对静态内部类CounterCell进行了修饰。

/* ---------------- Counter support -------------- */

/**
* A padded cell for distributing counts. Adapted from LongAdder
* and Striped64. See their internal docs for explanation.
*/
@sun.misc.Contended
static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}

Thread中:

/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed; /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe; /** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;

内存屏障:

内存屏障提供了3个功能:确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;

强制将对缓存的修改操作立即写入主存;

如果是写操作,它会导致其他CPU中对应的缓存行无效。

屏障的策略:

在每个volatile写操作前面插入storestore屏障;

在每个volatile写操作后面插入storeload屏障;

在每个volatile读操作后面插入loadload屏障;

在每个volatile读操作后面插入loadstore屏障;

其中loadload和loadstore对应的是方法acquire,storestore对应的是方法release,storeload对应的是方法fence。

理解:

工作内存可以看成是CPU高速缓存、寄存器的抽象

主内存可以看成就是物理硬件中主内存的抽象

参考:

Java併發編程-volatile

CPU Cache與緩存行

JAVA 拾遺 — CPU Cache 與緩存行

聊聊高并发(三十四)Java内存模型那些事(二)理解CPU高速缓存的工作原理

细说Cache-L1/L2/L3/TLB

CPU高速缓存的更多相关文章

  1. 充分利用CPU高速缓存,提高程序效率(原理篇)

    提高程序效率应该充分利用CPU的高速缓存.要想编写出对CPU缓存友好的程序就得先明白CPU高速缓存的运行机制. i5-2400S: 1.有三级缓存分别为 32k(数据.指令缓存分开,分为32k),25 ...

  2. 软硬件协同编程 - C#玩转CPU高速缓存(附示例)

    写在前面 好久没有写博客了,一直在不断地探索响应式DDD,又get到了很多新知识,解惑了很多老问题,最近读了Martin Fowler大师一篇非常精彩的博客The LMAX Architecture, ...

  3. CPU高速缓存行与内存关系 及并发MESI 协议

    先来一个整体图 一. 大致关系: CPU Cache --> 前端总线 FSB (下图中的Bus) --> Memory 内存 CPU 为了更快的执行代码.于是当从内存中读取数据时,并不是 ...

  4. a=a+1背后的内存模型和CPU高速缓存

    学过JAVA的人都知道,程序运行过程中的临时数据,都是从外部存储设备调入内存(物理内存)中,再进行读写操作的.而计算机在执行程序时,对程序的每条指令都是在CPU中执行的,而指令的执行,势必涉及到对数据 ...

  5. 每个程序员都应该了解的 CPU 高速缓存

    每个程序员都应该了解的 CPU 高速缓存 英文原文:Memory part 2: CPU caches 来源:oschina [编者按:这是Ulrich Drepper写“程序员都该知道存储器”的第二 ...

  6. linux查看CPU高速缓存(cache)信息

    一.Linux下查看CPU Cache级数,每级大小 dmesg | grep cache 实例结果如下: 二.查看Cache的关联方式 在 /sys/devices/system/cpu/中查看相应 ...

  7. CPU高速缓存行与内存关系 及并发MESI 协议(转载)

    原文链接 http://www.cnblogs.com/jokerjason/p/9584402.html 先来一个整体图 一. 大致关系: CPU Cache --> 前端总线 FSB (下图 ...

  8. 【缓存】CPU高速缓存 之MESI 性协议 Gif 动画

    CPU缓存架构 不同的CPU厂商的架构也有些不同,在这里只介绍流行的缓存架构 缓存一致性可以分为三个点: 在进程每个写入运算时都立刻采取措施保证资料一致性 每个独立的运算,假如它造成资料值的改变,所有 ...

  9. CPU阿甘之烦恼

    转自“码农翻身”公共号,原文地址CPU阿甘之烦恼 总结:(程序加载到内存运行的演变过程) 内存存放程序.OS负责加载程序到内存.CPU负责运行内存中的程序 1.串行:加载一个完整程序到内存,CPU运行 ...

随机推荐

  1. linux 的基本操作(编写shell 脚本)

    终于到shell 脚本这章了,在以前笔者卖了好多关子说shell脚本怎么怎么重要,确实shell脚本在linux系统管理员的运维工作中非常非常重要.下面笔者就带你正式进入shell脚本的世界吧. 到现 ...

  2. JBPM工作流(五)——执行流程实例

    概念: ProcessInstance,流程实例:代表流程定义的一次执行.如:张三昨天按请假流程请了一次假.一个流程实例包括了所有运行阶段,其中最典型的属性就是跟踪当前节点的指针,如下图. Execu ...

  3. http proxy模块参数

    http proxy模块参数 nginx功能的代理功能是是通过http proxy模块来实现的.默认在安装Nginx是已经安装了http proxy模块,可以直接使用. http模块相关参数 说明 p ...

  4. 网络爬虫基础知识(Python实现)

    浏览器的请求 url=请求协议(http/https)+网站域名+资源路径+参数 http:超文本传输协议(以明文的形式进行传输),传输效率高,但不安全. https:由http+ssl(安全套接子层 ...

  5. python全栈开发 * 24 知识点汇总 * 180705

    24 模块-------序列化一.什么是模块 模块:py文件就是一个模块.二.模块的分类:(1)内置模块 (登录模块,时间模块,sys模块,os模块)(2)扩展模块 (itchat 微信有关,爬虫,b ...

  6. 201621123049《java程序设计》第四周学习总结

    1. 本周学习总结 1.1 写出你认为本周学习中比较重要的知识点关键词 继承 类型转换 覆盖 1.2 尝试使用思维导图将这些关键词组织起来.注:思维导图一般不需要出现过多的字. 2. 书面作业 1. ...

  7. GIT 身份验证失败问题解决方案,由于修改密码产生的问题

    fatal: Authentication failed for 'http:xxxxxxxxxx.git/' 解决方案 1. git config --global user.name " ...

  8. Django + Axios & Ajax post和get 传参

    话说千遍淡如水,不如代码来一通.   Axios post: let params = new URLSearchParams(); params.append('id',xx) axios({ ur ...

  9. Linux-003-Resource temporarily unavailable

    Jenkins构建任务向服务器发送war包时提示信息如下所示: 由上述信息可知通过SSH命令连接失败.通过Client连接服务器,提示信息如下: 提示信息说明资源暂时不可用. 原因一般是因为用户或应用 ...

  10. SpringBoot整合Swagger2

    相信各位在公司写API文档数量应该不少,当然如果你还处在自己一个人开发前后台的年代,当我没说,如今为了前后台更好的对接,还是为了以后交接方便,都有要求写API文档. 手写Api文档的几个痛点: 文档需 ...