从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态众所周知, CPU是计算机的大脑, 它负责执行程序的指令; 内存负责存数据, 包括程序自身数据. 同样大家都知道, 内存比CPU慢很多. 其实在30年前, CPU的频率和内存总线的频率在同一个级别, 访问内存只比访问CPU寄存器慢一点儿. 由于内存的发展都到技术及成本的限制, 现在获取内存中的一条数据大概需要200多个CPU周期(CPU cycles), 而CPU寄存器一般情况下1个CPU周期就够了.
CPU缓存
网页浏览器为了加快速度,会在本机存缓存以前浏览过的数据; 传统数据库或NoSQL数据库为了加速查询, 常在内存设置一个缓存, 减少对磁盘(慢)的IO. 同样内存与CPU的速度相差太远, 于是CPU设计者们就给CPU加上了缓存(CPU Cache). 如果你需要对同一批数据操作很多次, 那么把数据放至离CPU更近的缓存, 会给程序带来很大的速度提升. 例如, 做一个循环计数, 把计数变量放到缓存里,就不用每次循环都往内存存取数据了. 下面是CPU Cache的简单示意图.
随着多核的发展, CPU Cache分成了三个级别: L1, L2, L3. 级别越小越接近CPU, 所以速度也更快, 同时也代表着容量越小. L1是最接近CPU的, 它容量最小, 例如32K, 速度最快,每个核上都有一个L1 Cache(准确地说每个核上有两个L1 Cache, 一个存数据 L1d Cache, 一个存指令 L1i Cache). L2 Cache 更大一些,例如256K, 速度要慢一些, 一般情况下每个核上都有一个独立的L2 Cache; L3 Cache是三级缓存中最大的一级,例如12MB,同时也是最慢的一级, 在同一个CPU插槽之间的核共享一个L3 Cache.
从CPU到 | 大约需要的CPU周期 | 大约需要的时间(单位ns) |
寄存器 | 1 cycle | |
L1 Cache | ~3-4 cycles | ~0.5-1 ns |
L2 Cache | ~10-20 cycles | ~3-7 ns |
L3 Cache | ~40-45 cycles | ~15 ns |
跨槽传输 | ~20 ns | |
内存 | ~120-240 cycles | ~60-120ns |
感兴趣的同学可以在Linux下面用cat /proc/cpuinfo, 或Ubuntu下lscpu看看自己机器的缓存情况, 更细的可以通过以下命令看看:
$ cat /sys/devices/system/cpu/cpu0/cache/index0/size
32K
$ cat /sys/devices/system/cpu/cpu0/cache/index0/type
Data
$ cat /sys/devices/system/cpu/cpu0/cache/index0/level
1
$ cat /sys/devices/system/cpu/cpu3/cache/index3/level
3
就像数据库cache一样, 获取数据时首先会在最快的cache中找数据, 如果没有命中(Cache miss) 则往下一级找, 直到三层Cache都找不到,那只要向内存要数据了. 一次次地未命中,代表取数据消耗的时间越长.
缓存行(Cache line)
为了高效地存取缓存, 不是简单随意地将单条数据写入缓存的. 缓存是由缓存行组成的, 典型的一行是64字节. 读者可以通过下面的shell命令,查看cherency_line_size就知道知道机器的缓存行是多大.
$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64
CPU存取缓存都是按行为最小单位操作的. 在这儿我将不提及缓存的associativity问题, 将问题简化一些. 一个Java long型占8字节, 所以从一条缓存行上你可以获取到8个long型变量. 所以如果你访问一个long型数组, 当有一个long被加载到cache中, 你将无消耗地加载了另外7个. 所以你可以非常快地遍历数组.实验及分析
我们在Java编程时, 如果不注意CPU Cache, 那么将导致程序效率低下. 例如以下程序, 有一个二维long型数组, 在我的32位笔记本上运行时的内存分布如图:
32位机器中的java的数组对象头共占16字节(详情见 链接), 加上62个long型一行long数据一共占512字节. 所以这个二维数据是顺序排列的.
public class L1CacheMiss {
private static final int RUNS = 10;
private static final int DIMENSION_1 = 1024 * 1024;
private static final int DIMENSION_2 = 62;
private static long[][] longs;
public static void main(String[] args) throws Exception {
Thread.sleep(10000);
longs = new long[DIMENSION_1][];
for (int i = 0; i < DIMENSION_1; i++) {
longs[i] = new long[DIMENSION_2];
for (int j = 0; j < DIMENSION_2; j++) {
longs[i][j] = 0L;
}
}
System.out.println(“starting….”);
final long start = System.nanoTime();
long sum = 0L;
for (int r = 0; r < RUNS; r++) {
// for (int j = 0; j < DIMENSION_2; j++) {
// for (int i = 0; i < DIMENSION_1; i++) {
// sum += longs[i][j];
// }
// }
for (int i = 0; i < DIMENSION_1; i++) {
for (int j = 0; j < DIMENSION_2; j++) {
sum += longs[i][j];
}
}
}
System.out.println(“duration = ” + (System.nanoTime() - start));
}
}
编译后运行,结果如下
$ java L1CacheMiss
starting….
duration = 1460583903
然后我们将22-26行的注释取消, 将28-32行注释, 编译后再次运行,结果是不是比我们预想得还糟?
$ java L1CacheMiss
starting….
duration = 22332686898
前面只花了1.4秒的程序, 只做一行的对调要运行22秒. 从上节我们可以知道在加载longs[i][j]时, longs[i][j+1]很可能也会被加载至cache中, 所以立即访问longs[i][j+1]将会命中L1 Cache, 而如果你访问longs[i+1][j]情况就不一样了, 这时候很可能会产生 cache miss导致效率低下.下面我们用perf来验证一下,先将快的程序跑一下.
$ perf stat -e L1-dcache-load-misses java L1CacheMiss
starting….
duration = 1463011588
Performance counter stats for ‘java L1CacheMiss’:
164,625,965 L1-dcache-load-misses
13.273572184 seconds time elapsed
一共164,625,965次L1 cache miss, 再看看慢的程序
$ perf stat -e L1-dcache-load-misses java L1CacheMiss
starting….
duration = 21095062165
Performance counter stats for ‘java L1CacheMiss’:
1,421,402,322 L1-dcache-load-misses
32.894789436 seconds time elapsed
这回产生了1,421,402,322次 L1-dcache-load-misses, 所以慢多了.
以上我只是示例了在L1 Cache满了之后才会发生的cache miss. 其实cache miss的原因有下面三种:
1. 第一次访问数据, 在cache中根本不存在这条数据, 所以cache miss, 可以通过prefetch解决.
2. cache冲突, 需要通过补齐来解决.
3. 就是我示例的这种, cache满, 一般情况下我们需要减少操作的数据大小, 尽量按数据的物理顺序访问数据.
具体的信息可以参考这篇论文.
下一篇将介绍CPU cache的另一种误区: 伪共享(False Sharing).
从Java视角理解CPU缓存(CPU Cache)的更多相关文章
-
从Java视角理解CPU上下文切换(Context Switch)
从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态 在高性能编程时,经常接触到多线程. 起初我们的理解是, 多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快 ...
-
从Java视角理解CPU缓存和伪共享
转载自:http://ifeve.com/from-javaeye-cpu-cache/ http://ifeve.com/from-javaeye-false-shari ...
-
什么是CPU缓存
一.什么是CPU缓存 1. CPU缓存的来历 众所周知,CPU是计算机的大脑,它负责执行程序的指令,而内存负责存数据, 包括程序自身的数据.在很多年前,CPU的频率与内存总线的频率在同一层面上.内存的 ...
-
保护模式篇——TLB与CPU缓存
写在前面 此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...
-
TLB和CPU缓存
TLB 如果每次应用程序访问一个线性地址都需要先解析(查PDT,PTT)那么效率十分低,为了提高执行效率CPU在CPU内部建立了一个TLB表,此表和寄存器一样访问速度极高.其会记录线性地址和物理地址之 ...
-
基于JVM原理、JMM模型和CPU缓存模型深入理解Java并发编程
许多以Java多线程开发为主题的技术书籍,都会把对Java虚拟机和Java内存模型的讲解,作为讲授Java并发编程开发的主要内容,有的还深入到计算机系统的内存.CPU.缓存等予以说明.实际上,在实际的 ...
-
【Java并发编程】从CPU缓存模型到JMM来理解volatile关键字
目录 并发编程三大特性 原子性 可见性 有序性 CPU缓存模型是什么 高速缓存为何出现? 缓存一致性问题 如何解决缓存不一致 JMM内存模型是什么 JMM的规定 Java对三大特性的保证 原子性 可见 ...
-
第三章 - CPU缓存结构和java内存模型
CPU 缓存结构原理 CPU 缓存结构 查看 cpu 缓存 速度比较 查看 cpu 缓存行 cpu 拿到的内存地址格式是这样的 CPU 缓存读 根据低位,计算在缓存中的索引 判断是否有效 0 去内存读 ...
-
【转】七个例子帮你更好地理解 CPU 缓存
我的大多数读者都知道缓存是一种快速.小型.存储最近已访问的内存的地方.这个描述相当准确,但是深入处理器缓存如何工作的"枯燥"细节,会对尝试理解程序性能有很大帮助. 在这篇博文中,我 ...
随机推荐
-
[笔记]HAproxy reload config file with uninterrupt session
HAProxy is a high performance load balancer. It is very light-weight, and free, making it a great op ...
-
tomcat部署方法总结
可以参考之前的:http://www.cnblogs.com/youxin/archive/2013/01/18/2865814.html 在Tomcat中部署Java Web应用程序有两种方式:静态 ...
-
BZOJ1937 [Shoi2004]Mst 最小生成树
首先由贪心的想法知道,树边只减不加,非树边只加不减,令$w_i$表示i号边原来的边权,$d_i$表示i号边的改变量 对于一条非树边$j$连接着两个点$x$.$y$,则对于$xy$这条路径上的所有树边$ ...
-
VS2013 调试MVC源码[MVC5.2.3+MVC4Web项目]
1.目前MVC源码版本为5.2.3,下回来后用VS2013打开,把System.Web.Mvc项目的版本号改为4.0.0.1 2.在解决方案下建一个MVC4项目,.NET选4.5,修改根目录以及Vie ...
-
Nginx CORS实现JS跨域
1. 什么是跨域 简单地理解就是因为JavaScript同源策略的限制,a.com 域名下的js无法操作b.com或是c.a.com域名下的对象. 同源是指相同的协议.域名.端口.特别注意两点: 如果 ...
-
hdu 1542 Atlantis(求矩形面积并)
分别记录x坐标和y坐标,将其分别按照从左到有的方向排序.然后对于一个输入的矩形的x,y坐标范围内的下标进行标记.以两个相邻的坐标为最小单位分割图形,最后求总面积. #include<stdio. ...
-
laravel学习资料
http://blog.csdn.net/qq_20873039/article/category/6246852 --laravel核心概念 /Eloquent ORM / laravel bl ...
-
浙江省赛之Singing Everywhere
题目:http://acm.zju.edu.cn/onlinejudge/showContestProblem.do?problemId=5996 方法: 在大佬的指导下完成. 寻找峰值,找到一共k个 ...
-
mysql学习之路_sql
查看数据库: Show databases; 查看指定部分数据库:模糊查询 Show databases like ‘patten’;--paatten是匹配模式 %:表示是匹配模式 _:表示匹配单个 ...
-
盒子模型与flex模型
一.盒子模型 注意:两个相邻元素的margin值是重叠在一起的,取当中最大的那个值. 水平方向auto, margin:0 auto;会居中 但是margin-left:auto;,元素会到最右 ...