写Java也得了解CPU缓存

标签: Java 多线程编程 算法数据结构 CPU缓存 行缓存 | 发表时间:2014-04-11 19:45 | 作者:童燕群
出处:http://shentar.me

CPU,一般认为写C/C++的才需要了解,写高级语言的(Java/C#/pathon…)并不需要了解那么底层的东西。我一开始也是这么想的,但直到碰到LMAX的 Disruptor,以及 马丁的博文,才发现写Java的,更加不能忽视CPU。经过一段时间的阅读,希望总结一下自己的阅读后的感悟。本文主要谈谈CPU缓存对Java编程的影响,不涉及具体CPU缓存的机制和实现。

现代CPU的缓存结构一般分三层,L1,L2和L3。如下图所示:

CPU三级缓存示意图

级别越小的缓存,越接近CPU, 意味着速度越快且容量越少。

L1是最接近CPU的,它容量最小,速度最快,每个核上都有一个L1 Cache(准确地说每个核上有两个L1 Cache, 一个存数据 L1d Cache, 一个存指令 L1i Cache);

L2 Cache 更大一些,例如256K,速度要慢一些,一般情况下每个核上都有一个独立的L2 Cache;

L3 Cache是三级缓存中最大的一级,例如12MB,同时也是最慢的一级,在同一个CPU插槽之间的核共享一个L3 Cache。

当CPU运作时,它首先去L1寻找它所需要的数据,然后去L2,然后去L3。如果三级缓存都没找到它需要的数据,则从内存里获取数据。寻找的路径越长,耗时越长。所以如果要非常频繁的获取某些数据,保证这些数据在L1缓存里。这样速度将非常快。下表表示了CPU到各缓存和内存之间的大概速度:

从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

利用CPU-Z可以查看CPU缓存的信息:

CPU-Z

在linux下可以使用下列命令查看proc文件系统或者sys下的设备描述。

有了上面对CPU的大概了解,我们来看看 缓存行(Cache line)。缓存,是由缓存行组成的。一般一行缓存行有64字节(由上图”64-byte line size”可知)。所以使用缓存时,并不是一个一个字节使用,而是一行缓存行、一行缓存行这样使用;换句话说,CPU存取缓存都是按照一行,为最小单位操作的。

这意味着,如果没有好好利用缓存行的话,程序可能会遇到性能的问题。可看下面的程序:

public class L1CacheMiss {
    private static final int RUNS = 10;
    private static final int DIMENSION_1 = 1024 * 1024;
    private static final int DIMENSION_2 = 6;

    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....");

        long sum = 0L;
        for (int r = 0; r < RUNS; r++) {

            final long start = System.nanoTime();

            //slow
//            for (int j = 0; j < DIMENSION_2; j++) {
//                for (int i = 0; i < DIMENSION_1; i++) {
//                    sum += longs[i][j];
//                }
//            }

            //fast
            for (int i = 0; i < DIMENSION_1; i++) {
                for (int j = 0; j < DIMENSION_2; j++) {
                    sum += longs[i][j];
                }
            }

            System.out.println((System.nanoTime() - start));
        }

    }
}

 

以我所使用的Xeon E3 CPU和64位操作系统和64位JVM为例,如 这里所说,假设编译器采用行主序存储数组。

64位系统,Java数组对象头固定占16字节(未证实),而long类型占8个字节。所以16+8*6=64字节,刚好等于一条缓存行的长度:

缓存行效果

如32-36行代码所示,每次开始内循环时,从内存抓取的数据块实际上覆盖了longs[i][0]到longs[i][5]的全部数据(刚好64字节)。因此,内循环时所有的数据都在L1缓存可以命中,遍历将非常快。

假如,将32-36行代码注释而用25-29行代码代替,那么将会造成大量的缓存失效。因为每次从内存抓取的都是同行不同列的数据块(如longs[i][0]到longs[i][5]的全部数据),但循环下一个的目标,却是同列不同行(如longs[0][0]下一个是longs[1][0],造成了longs[0][1]-longs[0][5]无法重复利用)。运行时间的差距如下图,单位是微秒(us):

程序运行效果对比

最后,我们都希望需要的数据都在L1缓存里,但事实上经常事与愿违,所以缓存失效 (Cache Miss)是常有的事,也是我们需要避免的事。

一般来说,缓存失效有三种情况:
1. 第一次访问数据, 在cache中根本不存在这条数据, 所以cache miss, 可以通过 prefetch解决。
2. cache冲突, 需要通过补齐来解决(伪共享的产生)。
3. cache满, 一般情况下我们需要减少操作的数据大小, 尽量按数据的物理顺序访问数据。

参考:
http://mechanitis.blogspot.hk/2011/07/dissecting-disruptor-why-its-so-fast_22.html
http://coderplay.iteye.com/blog/1485760
http://en.wikipedia.org/wiki/CPU_cache
转载信息: 原文地址

相关 [java cpu 缓存] 推荐:

写Java也得了解CPU缓存

- - 忘我的追寻
CPU,一般认为写C/C++的才需要了解,写高级语言的(Java/C#/pathon…)并不需要了解那么底层的东西. 我一开始也是这么想的,但直到碰到LMAX的 Disruptor,以及 马丁的博文,才发现写Java的,更加不能忽视CPU. 经过一段时间的阅读,希望总结一下自己的阅读后的感悟. 本文主要谈谈CPU缓存对Java编程的影响,不涉及具体CPU缓存的机制和实现.

从Java视角理解CPU缓存(CPU Cache)

- - 淘宝网通用产品团队博客
从Java视角理解系统结构连载, 关注我的微博( 链接)了解最新动态众所周知, CPU是计算机的大脑, 它负责执行程序的指令; 内存负责存数据, 包括程序自身数据. 同样大家都知道, 内存比CPU慢很多. 其实在30年前, CPU的频率和内存总线的频率在同一个级别, 访问内存只比访问CPU寄存器慢一点儿.

CPU缓存刷新的误解

- - 并发编程网 - ifeve.com
原文地址  作者: Mechanical Sympathy  译者:潘曦  校对: Simon-SZ ,方腾飞. 即使是资深的技术人员,我经常听到他们谈论某些操作是如何导致一个CPU缓存的刷新. 看来这是关于CPU缓存如何工作和缓存子系统如何与执行核心交互的一个常见误区. 本文将致力于解释CPU缓存的功能以及执行程序指令的CPU核心如何与缓存交互.

Java定位CPU使用100%的方法

- cong - 龙浩的blog
流程:把线程dump出来,然后分析. 1:Threaddump的方法:. jvisualvm中来thread dump.  对应的线程id是十进制的,需要转换为十六进制的在threaddump文件中才可以看到. 找到对应的线程,把相关的方法找出来,可以精确到代码的行号,自己修改相关源码来fix bug.

java在CPU中的一些个破事

- - ImportNew
还是那句话,貌似java和CPU没有多少关系,我们现在来聊聊有啥关系;. 这三个步骤来完成,从这点你也能看出i++为什么能实现先做其他的事情再自我加1,因为它讲值赋予给了另一个变量. 2、我们要用到多线程并发一致性,就需要用到锁的机制,目前类似Atomic*的东西基本可以满足这些要求,内部提供了很多unsafe类的方法,通过不断对比绝对可见性的数据来保证获取的数据是最新的;接下来我们继续来说一些CPU其他的事情.

从Java视角理解CPU上下文切换(Context Switch)

- - 淘宝网通用产品团队博客
从Java视角理解系统结构连载, 关注我的微博( 链接)了解最新动态在高性能编程时,经常接触到多线程. 起初我们的理解是, 多个线程并行地执行总比单个线程要快, 就像多个人一起干活总比一个人干要快. 然而实际情况是, 多线程之间需要竞争IO设备, 或者竞争锁资源,导致往往执行速度还不如单个线程. 在这里有一个经常提及的概念就是: 上下文切换(Context Switch).

JAVA获取计算机CPU、硬盘、主板、网络等信息

- - 编程语言 - ITeye博客
l转[ http://www.cnblogs.com/jifeng/archive/2012/05/16/2503519.html]. 通过使用第三方开源jar包sigar.jar我们可以获得本地的信息. 1.下载sigar.jar. 2.按照主页上的说明解压包后将相应的文件copy到java路径.

Linux下java进程CPU占用率高-分析方法

- - Linux - 操作系统 - ITeye博客
今天登陆同事的一台gateway 开始以为hive环境登陆不了了,仔细一看看了下是因为机器很卡,我每次等几秒没登陆就ctrl+c了,看了下是有个java进程cpu:340.4%  mem:14.6%. 一般解决方法是通过top命令找出消耗资源高的线程id,利用strace命令查看该线程所有系统调用.

快速定位消耗CPU的Java线程

- - BlogJava_首页
1.通过快递定位消耗CPU的线程.     工具: Window 平台 通过 Processor  explorer 获得线程ID .     Linux 通过 top -H 获得. 然后通过Thread dump 获得java 所有线程 搜索到相应的线程ID 定位该线程.     可以通过 Java 内置的jvisualvm  或者Jstack.

java问题导致linux负载、cpu过高如何定位

- - CSDN博客推荐文章
1.用top找到最耗资源的进程id. 2.查询最消耗资源的java进程. 3.打印java 栈 信息. 4.将耗资源的javaPID转换为16进制(5920转1720<16进制>  去百度找 :十进制转十六进制). PID 对应 堆栈中的nid(16进制). 去stack.txt 中查找nid=1720的问题.