∇ \nabla ∇ 联系方式:

e-mail: FesianXu@gmail.com

QQ: 973926198

github: https://github.com/FesianXu

知乎专栏: 计算机视觉/计算机图形理论与应用

微信公众号


在图像处理相关的代码中,我们经常有类似于以下的代码,去遍历多维数组(张量)的每一个元素:

#define LENGTH 10000
void proc(){uint8 datas[LENGTH][LENGTH];int i, j;long long sum = 0;for (i = 0; i < LENGTH; i++){for (j = 0; j < LENGTH; j++){sum += datas[i][j];}}
}

其中的sum += datas[i][j];从语义上是和sum += datas[j][i];效果一致的,然而从性能上来说是否是一致的呢?答案是不是的,要看程序的编译优化程度。我们会发现,当循环变量处于内循环和外循环时,其性能是不一样的,即便是运算结果一致。


我们知道不管是一维数组还是多维数组,在内存中都是线性排列的,以二维数组为例子,为了能将二维的数组拉成一维的,一般需要考虑编译器在编译代码时,其在内存中是行优先(row-major)排列还是列优先(colum-major)排列的。如下图所示,如果一个数组是行优先排列的,那么其在连续内存上的排列顺序如红色线的顺序。举个例子,比如现在有个数组int vars[3][3],如果是行优先排列的,那么有:(===的意思是等价, (vars+i)表示对以vars作为数组指针的前提下,偏移i个元素的地址,而*(vars+i)是对其进行取内容。)

vars[0, 0] === *(vars+0);
vars[0, 1] === *(vars+1);
vars[1, 0] === *(vars+3);
==>
vars[i, j] === *(vars+3*i+j)

如果是列优先排列呢,则是

vars[0, 0] === *(vars+0);
vars[0, 1] === *(vars+1*3+1);
vars[1, 0] === *(vars+0*3+1);
==>
vars[i, j] === *(vars+3*j+i)


到这里为止都好理解,不过后续我们需要理解的一点是,我们现在跑得程序很多都不是在裸机上跑的。这里指的裸机就是没有操作系统的计算机,比如单片机等,或者是没有任何操作系统的其他CPU。在操作系统上跑程序,那么我们的程序的内存空间其实都是虚拟内存空间,是一种逻辑地址,其和物理的内存位置是没有必然关联的,需要受到操作系统的控制。采用虚拟内存有很多好处,其中最明显的就是:

  1. 不需要考虑不同程序之间的相对地址偏移,每个程序都有其独自的内存空间,其地址范围都是从0到系统定义的最大值max_mem
  2. 虚拟内存可以看成是一个巨大的线性内存空间,不需要考虑内存不足的情况,因为当内存不足的情况或者需要访问的数据不在内存上时,就发生了缺页错误(page fault),操作系统会自动进行“换页”(paging),将物理内存暂时不用了的内存页保存到存储空间巨大的硬盘上,然后把需要读取的内存加载到内存上。在这种情况下,可以把整个硬盘都看成是一个可以换入和切出的内存(虽然速度很慢,没法和真正的内存比)

虚拟内存的细节太多,是操作系统设计的一个主要概念,其他细节需要参考其他书籍,比如[1,2]。我们在这里需要知道的是,我们程序中随时可能遇到数据在内存中找不到的事情发生,这个时候就会发生换页的操作,从硬盘中加载数据到内存(IO操作)是非常花时间的,因此很多程序的瓶颈都会在此。我们这里的关键点也正是在此。

在一个以行优先排序的编译器上,每一行的数据在内存地址上都是比较接近的,而列数据的地址总是相差着sizeof(data_type) * N,其中 N N N是每一行的元素数量。这就导致每一行的数据可能是处于同一个内存页帧(page frame)上的,而每一个列的数据处于不同的页帧上。在以行优先排序的编译器上,如果外循环是列索引,内循环是行索引,有可能会发生频繁地进行缺页,换页的操作(准确地说是n*m次),严重影响程序的性能。当然,这里只是可能,具体情况和你的数组大小,系统的页大小设置等有关。(这里的缺页特别在物理内存比较小的时候更为严重)

这里举个代码例子,说明性能上的差别。


考虑代码:

# code 1
#define LENGTH 20000
int main(){float vector[LENGTH][LENGTH];int i, j;float sum = 0.0f;for (i = 0; i < LENGTH; i++){for (j = 0; j < LENGTH; j++){sum += datas[i][j];}}return 0;
}
# code 2
#define LENGTH 20000
int main(){float vector[LENGTH][LENGTH];int i, j;float sum = 0.0f;for (i = 0; i < LENGTH; i++){for (j = 0; j < LENGTH; j++){sum += datas[j][i];}}return 0;
}

这两个代码除了索引的顺序不同(相当于内外循环调换)之外,其他别无差别。我们为了防止编译器对代码进行优化,影响分析,我们采用-O0编译参数以去掉任何gcc的优化。(其实用其他优化等级效果也是类似的,读者可以自行尝试,并且用指令gcc -O0 -S code1.c观察分析其汇编代码。)

gcc -O0 code1.c
/usr/bin/time -v ./a.out

我们用/usr/bin/time分析程序的运行时间,我们有这两者的运行时间分别为:


code 1的运行时间为2.05s

而code 2的时间则变成了5.68s,性能明显差code 1很多。

但是如果我们把数组的大小从20000改成200会怎么样呢?我们发现其性能将不会有明显区别了,就是因为尺寸大小的不同影响了页帧的换页过程。


Reference

[1]. Bryant R E, David Richard O H, David Richard O H. Computer systems: a programmer’s perspective[M]. Upper Saddle River: Prentice Hall, 2003.
[2]. Tanenbaum A S. Structured computer organization[M]. Pearson Education India, 2016.

c语言中内循环和外循环的位置可能产生性能上的区别相关推荐

  1. c语言内循环外循环怎么使用,开高速, 用内循环还是外循环? 教你正确使用内外循环!...

    不管是冬天天气太冷或者是防雾霾,还是夏天天气太热,开车的朋友会条件反射的把车内空调打开.其实,在开空调的同时,开内外循环也是很重要的. 有些车主朋友,车子开了小半年,什么时候开内外循环还是弄不清楚. ...

  2. c语言内循环和外循环作用是什么,内循环和外循环的区别是什么 你平时都用开哪个...

    冬天暖风系统的开启也有很多门道,很多老司机也会出错,冬天我们是开内循环还是外循环比较好呢?哪样对车的使用寿命能够更好的提高,今天小编就给大家说一下其中的门道,让我们在以后开车的时候注意使用,看一下效果 ...

  3. c语言外循环和内循环区别是什么意思,内循环和外循环的区别 内循环和外循环的正确使用方法...

    过完五一,天气要慢慢热起来了,汽车空调使用的季节又要来了,但是很多车主对汽车空调的内外循环使用还是有点傻傻分不清,今天汽车维修网小编就和大家简单的说一下内循环和外循环的 内循环和外循环标志 内循环和外 ...

  4. chatgpt赋能python:Python中如何实现内循环到外循环

    Python中如何实现内循环到外循环 Python是一种广泛使用的编程语言,其文本解析和数据结构操作灵活,让Python编程变得非常简单.在Python编程中,内循环到外循环是常见的问题,因此本文将着 ...

  5. SQL中内连接、外连接、交叉连接

    SQL中内连接.外连接.交叉连接 SQL连接可以分为内连接.外连接.交叉连接. 数据库数据:            book表                                      ...

  6. C语言中内联函数的作用 inline

    C语言中内联函数的作用 inline C语言中内联函数到底有什么作用? 试想一下,每当我们在假设就在主函数中调用另外一个函数的时候,那么这个函数就要入栈或者出栈,比如说下面的一个例子: 点击(此处)折 ...

  7. linux c语言编程内嵌汇编,gcc编译c语言中内嵌汇编

    gcc编译c语言中内嵌汇编 --AT&T and Intel 汇编语法对照 寄存器命名: AT&T:  %eax Intel: eax AT&T 语法源地址在左侧,目的地址在右 ...

  8. C语言中内嵌汇编asm语法

    这篇文章写得炒鸡详细而且很全面,易于理解,建议新手查看 C语言中内嵌汇编asm语法 下面这两篇文章作为补充来看 C语言内嵌汇编:asm volatile C语言ASM汇编内嵌语法

  9. Java中for循环嵌套的内循环和外循环

    关于for循环嵌套作如下解释: 首先内层循环属于外层循环循环体的一部分,当循环体执行完以后外层循环才进入第二次循环,此过程中内层循环需要执行符合条件的完整循环.(外循环控制行数,内循环控制每一行的个数 ...

最新文章

  1. 电脑内存和磁盘空间有什么区别与联系
  2. 【Android 应用开发】Paint 滤镜原理 之 图像结构 ( 图片文件二进制分析 | PNG文件结构 | 数据块结构 | IHDR 数据块详解 )
  3. 常态化疫情防控下会展经济的“长沙蓝本”
  4. 深入理解ajax系列第六篇——头部信息
  5. 使用Custom.pll修改标准Form的LOV
  6. 三维重建9:点云图像的滤波方法小结
  7. java 3number_java 数据Number、Math
  8. 武魂觉醒s系列服务器,[多线]星河斗罗——新服开荒丨高程度剧情还原丨3D坐骑丨魂环丨武魂觉醒[1.12.2]...
  9. STM32利用库函数驱动OLED
  10. 装了linux开机出现错误,[已解决]安装完,启动出现错误!
  11. matlab中用数据拟合圆心,拟合圆并求圆心(matlab)
  12. 生物聚集细胞生物化学反应的组织者Biomolecular condensates: organizers of cellular biochemistry
  13. Decode Ways
  14. 最齐全的装饰贴图素材,速来收藏
  15. U8普及版在win7系统中,UFO报表一点打印或预览就报错
  16. 紫猫插件php,简易中控紫猫插件版(3)压缩包使用说明
  17. 使用iperf测试网速
  18. win10关闭自动更新
  19. AT89C51单片机共阳极数码管动态显示(汇编语言)
  20. 后期维特根斯坦的语境观“:语言游戏”与“生活形式”

热门文章

  1. 量化系统交易者想要取得长远的成功需要具备什么条件呢?
  2. python壁纸数据抓取_python爬虫多线程实战:爬取美桌1080p壁纸图片
  3. 纯前端vue利用docxtemplater实现生成word文档下载 word模板,勾选框的默认勾选。。
  4. C语言z(1 3),C语言练习题z(1-3章).doc
  5. iphone查看手机信号强度教程「iphone技巧」
  6. 解决鼠标指针移动时出现停顿卡的原因
  7. 函数依赖——候选码【软件设计师中级】
  8. 面试专题-----计算机网络常考(五)
  9. vlive android app,vlive app官方版
  10. 热门软件看点:IE和它的对手们