(原)一段看似美丽的for循环,背后又隐藏着什么
之前很长一段时间,潜心修炼汇编,专门装了一个dos7,慢慢玩到win32汇编,再到linux的AT&A汇编,尝试写mbr的时候期间好几次把centos弄的开不了机,又莫名其妙的修好了,如今最大的感触就是:球莫名堂,还不如写JAVA。
对于比较高层的语言来说,都不会太在意底层是如何运作的,这是个好事,也是个坏事,好事是不用关心底层的繁琐的事情,只需聚焦到业务实现,坏处就是出现比较严重的问题难以排错,很容易出现看起来很漂亮但就是性能很渣的代码。
有如下两段代码:
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];
}
}
看起来长的一样是不是?两段代码看起来都没啥问题是吧,相信很多人都或多或少的撸过这样的两段代码,但是这两段代码的运行效率比较是:
第二段代码执行效率比第一段代码低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循环。
当我们撸了如下一行代码的时候:
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 全部失效,由此带来的性能是极低的。
撸代码的时候,且撸且小心。。。
(原)一段看似美丽的for循环,背后又隐藏着什么的更多相关文章
- 美丽的for循环语句
美丽的for循环语句 题目:用for循环语句实现四个三角形不同的形状. 图案: ---------------第一个三角形图形形状----------------**********第二个三 ...
- 浅谈《剑指offer》原题:不使用条件、循环语句求1+2+……+n
转载自:浅谈<剑指offer>原题:求1+2+--+n 如侵犯您的版权,请联系:windeal12@qq.com <剑指offer>上的一道原题,求1+2+--+n,要求不能使 ...
- 统计无符号整数二进制中1的个数(Hamming weight)
1.问题来源 之所以来记录这个问题的解法,是因为在在线编程中经常遇到,比如编程之美和京东的校招笔试以及很多其他公司都累此不疲的出这个考题.看似简单的问题,背后却隐藏着很多精妙的解法.查找网上资料,才知 ...
- opengl入门学习
OpenGL入门学习 说起编程作图,大概还有很多人想起TC的#include <graphics.h>吧? 但是各位是否想过,那些画面绚丽的PC游戏是如何编写出来的?就靠TC那可怜的640 ...
- OpenGL入门学习(转)
OpenGL入门学习 http://www.cppblog.com/doing5552/archive/2009/01/08/71532.html 说起编程作图,大概还有很多人想起TC的#includ ...
- OpenGL理解
说起编程作图,大概还有很多人想起TC的#include <graphics.h>吧? 但是各位是否想过,那些画面绚丽的PC游戏是如何编写出来的?就靠TC那可怜的640*480分辨率.16色 ...
- OpenGL入门学习(转载)
说起编程作图,大概还有很多人想起TC的#include <graphics.h>吧? 但是各位是否想过,那些画面绚丽的PC游戏是如何编写出来的?就靠TC那可怜的640*480分辨率.16色 ...
- OpenGL------在Windows系统中显示文字
增加了两个文件,showline.c, showtext.c.分别为第二个和第三个示例程序的main函数相关部分.在ctbuf.h和textarea.h最开头部分增加了一句#include <s ...
- .CN根域名被攻击至瘫痪,谁之过?【转】
2013年8月25日凌晨,.CN域名凌晨出现大范围解析故障,经分析.CN的根域授权DNS全线故障,导致大面积.CN域名无法解析.事故造成大量以.cn和.com.cn结尾的域名无法访问.直到当日凌晨4点 ...
随机推荐
- POJ 最小球覆盖 模拟退火
最小球覆盖:用半径最小的球去覆盖所有点. 纯粹的退火算法,是搞不定的,精度不够,不然就会TLE,根本跑不出答案来. 任取一点为球心,然后一点点靠近最远点.其实这才是最主要的. 因为:4个点确定一个球, ...
- SiteMesh配置下载使用(简单介绍)
简单介绍 SiteMesh 是一个网页布局和修饰的框架,利用它可以将网页的内容和页面结构分离,以达到页面结构共享的目的. Sitemesh是由一个基于Web页面布局.装饰以及与现存Web应用整合的框架 ...
- 【办公-Word-VB】人民币大写转换-带完整注释
完整代码见:我的CSDN博客 -------------------- 应公司财务人员的请求,需在Word中做个:输入阿拉伯数字,自动转换成大写,并填充到Word控件中对应的亿.万.千控件格子的功能, ...
- Tomcat 服务器体系结构
connector 监听端口,监听到以后,交给 Engine 引擎 处理,引擎会根据请求找到对应的主机,找到主机后再去找对应的应用. 如果我们将 port 改为 80,那访问的时候就不用输入端口号,因 ...
- C# http post、get请求
public string HttpPostJson(string Url, string paramData, Dictionary<string, string> headerDic ...
- Shiro 登录认证源码详解
Shiro 登录认证源码详解 Apache Shiro 是一个强大且灵活的 Java 开源安全框架,拥有登录认证.授权管理.企业级会话管理和加密等功能,相比 Spring Security 来说要更加 ...
- bzoj2982: combination(lucas)
Description LMZ有n个不同的基友,他每天晚上要选m个进行[河蟹],而且要求每天晚上的选择都不一样.那么LMZ能够持续多少个这样的夜晚呢?当然,LMZ的一年有10007天,所以他想知道答案 ...
- SAP物料主数据的屏幕字段控制,必输,隐藏
http://www.cnblogs.com/275147378abc/p/5699077.html 1.事务码MM01,把物料组设为选填字段. 2.找到物料组的屏幕字段. 3.在后台根据屏幕字段找到 ...
- maven 打包失败 提示找不到jar的问题
服务打包的时候发现打包失败,查看原因大概是找不到jar包,但是本地仓库有.最终的解决的办法是使用maven 命令重新把jar包安装到本地仓库. 命令如下:mvn install:install-fil ...
- 迷你MyBank
该迷你MyBank,存贮是用对象数组来存贮的,所以比较简单,容易理解,适合新手.. 一.创建chengyuan类,在其中声明所需的成员变量: public class chengyuan { //该类 ...