以前很长一段时间,潜心修炼汇编,专门装了一个dos7,慢慢玩到win32汇编,再到linux的AT&A汇编,尝试写mbr的时候期间好几回把centos弄的开不了机,又莫名其妙的修好了,现在最大的感触就是:球莫名堂,还不如写JAVA。linux
对于比较高层的语言来讲,都不会太在乎底层是如何运做的,这是个好事,也是个坏事,好事是不用关心底层的繁琐的事情,只需聚焦到业务实现,坏处就是出现比较严重的问题难以排错,很容易出现看起来很漂亮但就是性能很渣的代码。程序员
有以下两段代码:编程
for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k = longs[i][j]; } }
for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k1 = longs[j][i]; } }
看起来长的同样是否是?两段代码看起来都没啥问题是吧,相信不少人都或多或少的撸过这样的两段代码,可是这两段代码的运行效率比较是:centos
第二段代码执行效率比第一段代码低300倍数组
完整的测试代码:缓存
public class RepeatIterator { private static final int ARRAY_SIZE = 10240; private Long[][] longs = new Long[ARRAY_SIZE][ARRAY_SIZE]; public static void main(String[] args) { new RepeatIterator().iteratorByRow(); new RepeatIterator().iteratorByColumn(); } private void iteratorByRow() {long start = System.currentTimeMillis(); for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k = longs[i][j]; } } System.out.println("iterator by row:" + (System.currentTimeMillis() - start)); } private void iteratorByColumn() {long start = System.currentTimeMillis(); for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k1 = longs[j][i]; } } System.out.println("iterator by column:" + (System.currentTimeMillis() - start)); } }
执行结果:服务器
iterator by row:6 iterator by column:1737 Process finished with exit code 0
代码为什么执行缓慢,机器为什么频繁卡死,服务器为什么屡屡宕机,看似美丽的代码背后又隐藏着什么,这一切的背后,是程序员人性的扭曲仍是道德的沦丧,是码农愤怒的爆发仍是饥渴的无奈,让咱们跟随镜头走进计算机的心里世界,解刨那一段小巧的for循环。dom
当咱们撸了以下一行代码的时候:性能
private static final int ARRAY_SIZE = 10240; private Long[][] longs = new Long[ARRAY_SIZE][ARRAY_SIZE];
在计算机的内存里面是以下分布(至少在个人计算机里面是这样分布的):测试
能够明确的看到在内存中的数组大小为10240,也就是咱们定义的大小,以及他的的地址(这并非实际的物理地址,8086里面是段的偏移地址,i386里面是分页地址),可是当遍历该数组的时候,并非直接从内存地址中取出这些数据,由于内存对于cpu来讲:太慢了。为了充分利用cpu的效率,因而人们设计出了cpu缓存,目前已经存在三级cpu缓存,而不一样的缓存意义并不同,特别是写多核编程的时候,若是对cpu缓存的理解不到位,很容易死在伪共享里面。
一个具备三级缓存的图示以下:
其中1级缓存并非一块缓存,而是2个部分,分别为代码缓存和数据缓存,1级和2级缓存为单个cpu独享,其余cpu不能修改到里面的数据,而3级缓存,则为多个cpu共享,而cpu伪共享,也是发生在这个位置,程序定义的数据,大多时候缓存在3级缓存,缓存也是行导向存储,经过以下方式能够查看一行缓存可以存储多少数据:
cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
64
64表明64个字节,一个Long对象的长度是8个字节,那么64个字节能够缓存8个Long,数组在内存中是一片连续的地址空间(物理也许不必定,但逻辑地址必定连续),这就意味着若是定义个一个8个长度的Long数组,当访问第一个数组元素被添加到缓存的时候,那么其余7个顺带的0消耗的就加载到了缓存中,这时候若是访问数组,那么速度是最高效的。也就是意味着,要充分利用缓存的特性,数据已定要按照行访问,不然会形成cache miss,这时候会从内存中获取数据,而且计算是否须要将其缓存,会极大的下降速度。
在上面的例子中,定义的二维数组,当使用第一种方式访问的时候,会发生以下状况:
1.访问第一行第一个元素,若是缓存中不存在(cache miss),从内存中获取,而且将其相邻的元素同时缓存。
2.访问第一行第二个元素,直接缓存取出(cache命中)
举个例子:
public class CacheLoad { private static final int ARRAY_SIZE = 10240; private Long[][] longs = null; public static void main(String[] args) { new CacheLoad().iterator(); new CacheLoad().iterator(); } private void iterator() { if (longs == null) { longs = new Long[ARRAY_SIZE][ARRAY_SIZE]; for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { longs[i][j] = new Random().nextLong(); } } } long start = System.currentTimeMillis(); for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k = longs[i][j]; } } System.out.println("iterator:" + (System.currentTimeMillis() - start)); } }
iterator:5 iterator:1 Process finished with exit code 0
第二次的查询速度理论(实际可能会大于,由于cpu线程切换,访问过程当中可能被系统其余资源抢占cpu)是小于等于第一次,由于会减小将第一个元素缓存的时间,另外并非所有的数据都会尽缓存,这不是程序所能控制。
当咱们采起第二种方式访问的时候,会发生以下状况:
1.访问第一行第一个元素,若是缓存中不存在(cache miss),从内存中获取,而且将其相邻的元素同时缓存。
2.访问第二行第一个元素,若是缓存中不存在(cache miss),从内存中获取,而且将其相邻的元素同时缓存。
。。。。。。。
由此能够看到,采用第二种方式访问数组的时候,很大的几率会形成cache miss,第二条cache冲掉第一条cache,极端状况是每次都miss,而且不管执行多少次,始终会miss,例如:
public class CacheLoad { private static final int ARRAY_SIZE = 10240; private Long[][] longs = new Long[ARRAY_SIZE][ARRAY_SIZE];; public static void main(String[] args) { new CacheLoad().iterator(); new CacheLoad().iterator(); new CacheLoad().iterator(); new CacheLoad().iterator(); } private void iterator() { long start = System.currentTimeMillis(); for (int i = 0; i < longs.length; i++) { for (int j = 0; j < longs[i].length; j++) { Long k = longs[j][i]; } } System.out.println("iterator:" + (System.currentTimeMillis() - start)); } }
iterator:1658 iterator:1697 iterator:1915 iterator:1728 Process finished with exit code 0
能够看到不管执行多少次,速度并不会所以变快,能够看见几本cache 所有失效,由此带来的性能是极低的。
撸代码的时候,且撸且当心。。。