在这一节内容开始以前,咱们先来看一个3行的小程序。你能够猜一猜,这个程序里的循环1和循环2,运行所花费的时间会差多少?你能够先思考几分钟,而后再看我下面的解释java
int[] arr = new int[64 * 1024 * 1024]; // 循环 1 for (int i = 0; i < arr.length; i++) arr[i] *= 3; // 循环 2 for (int i = 0; i < arr.length; i += 16) arr[i] *= 3
在这段Java程序中,咱们首先构造了一个64×1024×1024大小的整型数组。在循环1里,咱们遍历整个数组,将数组中每一项的值变成了原来的3倍;在循环2里,小程序
咱们每隔16个索引访问一个数组元素,将这一项的值变成了原来的3倍。
数组
按道理来讲,循环2只访问循环1中1/16的数组元素,只进行了循环1中1/16的乘法计算,那循环2花费的时间应该是循环1的1/16左右。缓存
可是实际上,循环1在个人电脑上运行须要50毫秒,循环2只须要46毫秒。这两个循环花费时间之差在15%以内。网络
为何会有这15%的差别呢?这和咱们今天要讲的CPU Cache有关。以前咱们看到了内存和硬盘之间存在的巨大性能差别。在CPU眼里,内存也慢得不行。
因而,聪明的工程师们就在CPU里面嵌入了CPU Cache(高速缓存),来解决这一问题。数据结构
好比说,咱们的主内存被分红0~31号这样32个块、咱们一共有8个缓存块。用户想要访问第21号内存块性能
若是21号内存块内容在缓存块中的话,它必定在5号缓存块(21 mod 8 = 5)中spa
刚才我花了不少篇幅,讲了CPU和内存之间的性能差别,以及咱们如何经过CPU Cache来尽量解决这二者之间的性能鸿沟。
你可能要问了,这样作的意义和价值到底是什么?毕竟,一次内存的访问,只不过须要100纳秒而已。1秒钟时间内,足有1000万个100纳秒。
别着急,咱们先来看一个故事。设计
2008年,一家叫做Spread Networks的通讯公司花费3亿美圆,作了一个光缆建设项目。目标是建设一条从芝加哥到新泽西总长1331千米的光缆线路。建设这条线路的目的,
实际上是为了将两地之间原有的网络访问延时,从17毫秒下降到13毫秒。
你可能会说,仅仅缩短了4毫秒时间啊,却花费3个亿,真的值吗?为这4毫秒时间买单的,实际上是一批高频交易公司。它们以5年1400万美圆的价格,使用这条线路。利用这短短的4毫秒的时间优点,这些公司经过高性能的计算机程序,在芝加哥和新泽西两地的交易所进行高频套利,以得到每一年以10亿美圆计的利润。如今你还以为这个不值得吗?
其实,只要350微秒的差别,就足够高频交易公司用来进行无风险套利了。而350微秒,若是用来进行100纳秒一次的内存访问,大约只够进行3500次。
而引入CPU Cache以后,咱们能够进行的数据访问次数,提高了数十倍,使得各类交易策略成为可能。3d
不少时候,程序的性能瓶颈,来自使用DRAM芯片的内存访问速度。
根据摩尔定律,自上世纪80年代以来,CPU和内存的性能鸿沟越拉越大。因而,现代CPU的设计者们,直接在CPU中嵌入了使用更高性能的SRAM芯片的Cache,
来弥补这一性能差别。经过巧妙地将内存地址,拆分红“索引+组标记+偏移量”的方式,使得咱们能够将很大的内存地址,映射到很小的CPU Cache地址里。
而CPU Cache带来的毫秒乃至微秒级别的性能差别,又能带来巨大的商业利益,十多年前的高频交易行业就是最好的例子。
在搞清楚从内存加载数据到Cache,以及从Cache里读取到想要的数据以后,咱们又要面临一个新的挑战了。CPU不只要读数据,还须要写数据,
咱们不能只把数据写入到Cache里面就结束了。下一讲,咱们就来仔细讲讲,CPU要写入数据的时候,怎么既不牺牲性能,又能保证数据的一致性。