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. [Educational Round 3][Codeforces 609E. Minimum spanning tree for each edge]

    这题本来是想放在educational round 3的题解里的,但觉得很有意思就单独拿出来写了 题目链接:609E - Minimum spanning tree for each edge 题目大 ...

  2. Python学习之旅(二十)

    Python基础知识(19):面向对象高级编程(Ⅱ) 定制类 形如“__xx__”的变量或函数在Python中是有特殊用途的 1.__str__ 让打印出来的结果更好看 __str__:面向用户:__ ...

  3. Intellij IDEA 生成返回值对象快捷键

    在编写一行JAVA语句时,有返回值的方法已经决定了返回对象的类型和泛型类型,我们只需要给这个对象起个名字就行. 如果使用快捷键生成这个返回值,我们就可以减少不必要的打字和思考,专注于过程的实现. 步骤 ...

  4. Oracle的基本查询知识

    基本语法 SELECT [DISTINCT] {*, column [alias],...} FROM table;参数说明SELECT 标识出所需的数据列.函数.常量和表达式.Distinct 删除 ...

  5. AngularJS资源合集[备忘]【申明:来源于网络】

    AngularJS资源合集[备忘][申明:来源于网络] 地址:http://blog.csdn.net/allgis/article/details/44646597

  6. Linux网络 - 数据包的接收过程(转)

    https://segmentfault.com/a/1190000008836467

  7. 2019年3月8日A股百点暴跌行情思考

    本人操作: [海通证券]:早盘挂单并撤单,盘中高位卖出,尾盘低位接回. 总结 - 正确:持股数量不变,成本降低. [信雅达]:早盘低开加仓,盘中高位卖出,跌后接回,尾盘跌停. 总结 -  正确:加仓, ...

  8. LeetCode 520 Detect Capital 解题报告

    题目要求 Given a word, you need to judge whether the usage of capitals in it is right or not. We define ...

  9. 数据格式XML、JSON详解

    一. XML数据格式 1.1 XML的定义  扩展标记语言 (Extensible Markup Language, XML) ,用于标记电子文件使其具有结构性的标记语言,可以用来标记数据.定义数据类 ...

  10. iOS学习之Object-C语言属性和点语法(转载收集)

    一.属性      1.属性的声明:在.h文件中使用@property声明属性.           @property NSString *name;       2.属性的作用是生成setter以 ...