一、写在前面
作为一个初级而且想成为高级的程序员,对浩瀚的代码世界总是充满的好奇与学习的动力,哈哈,别笑,我是认真的。最近之所以频繁的更新博文,而且知识点相对比较广,但大多也是C++各方面的基础,再加上最近时不时度一些别人在微信公众号上推送的技术文章(如果你也想了解这些技术公众号,也可以关注我,我反正觉得蛮好的,查看这位博友的博文吧程序猿关注的微信公众号和网站),就产生了一些技术联想,恰好又在写代码的过程中用到了或遇到了,索性就把这些都记录下来吧。
二、CPU指令执行局部性原理
一个编写良好的计算机程序代码常常具有良好的局部性,也就是说它们倾向于引用最近引用过的数据项本身,或者引用与最近引用过的数据项在内存空间(关于内存空间的解释请看一幅图理解计算机系统硬件组成)中相隔比较近的数据项,这种倾向性称为局部性原理。局部性原理包含两个方面的内容:时间局部性和空间局部性。所谓时间局部性指的是当前被引用的这个变量很可能在不久的将来(即几个机器指令周期之后)在此被引用;而空间局部性指的是CPU很可能在不久的将来引用与当前所引用变量内存空间中相邻的变量。这里举例两个时间局部性和空间局部性的例子,关于时间局部性,比如一个函数定义如下:
void fun(int n)
{
//数组a[100]是一个全局数组
for(int i=0; i<100; i++)
{
a[i] = a[i] + n;
}
}
如果调用函数fun(10),则在for循环内CPU每次都需要重新读取n的值(尽管现在一些编译器会做一定的优化),
这就不是一个时间局部性良好的代码,可以修改为如下:
void fun(int n)
{
int a = n; //定义局部变量,接管实参n的值,避免CPU每次都重新读取n的值,保证时间局部性
//数组a[100]是一个全局数组
for(int i=0; i<100; i++)
{
a[i] = a[i] + n;
}
}
void fun_1(int *a[M], int N)
{
//N为二维数组行数,M为二维数组列数
for(int i=0; i<N; i++)
{
for(int j=0; j<M; j++)
{
a[i][j] = a[i][j] + 1;
}
}
}
void fun_2(int *a[M], int N)
{
//N为二维数组行数,M为二维数组列数
for(int i=0; i<M; i++)
{
for(int j=0; j<N; j++)
{
a[i][j] = a[i][j] + 1;
}
}
}
比较函数fun_1()和fun_2(),可以发现,他们的函数体基本相同,都是包含两个for循环,也就是遍历二维数组的各个元素,但是
函数fun_1()比fun_2()的空间局部性要好,为什么呢?因为二维数组在计算机内部是按行存储的。
三、C++中尽量使用const引用形参
为什么呢?我的理解这也是为了保证时间局部性,在上面的例子中,我提到通过定义局部变量,即拷贝实参来保证时间局部性,但是当实参是一个很大的数组(数组无法拷贝)、或者是一个没有定义拷贝构造函数(关于C++拷贝构造函数可以链接深入理解C++对象模型之拷贝构造函数)的对象(这时编译器需要决定是否要合成拷贝构造函数,然后再执行相应的拷贝操作),但是拷贝的过程需要更长的时间消耗,本来定义一个局部变量是一种以内存空间换时间的概念,但是现在局部变量的拷贝时间太长,导致内存空间被消耗,同时并没有换取时间的减少,所以是得不偿失的,那怎么办呢?C++提供了const关键字,const关键字的作用很多,这里只解释它作为函数形参的一个作用,主要有两个作用:(1)它告诉编译器,这个参数是一个常量,首先你在函数内部不能改变它;(2)其次,如果在函数内部需要多次引用这个值,CPU不必每次都重新读取,直接使用第一次读取的值(我想应该是存放在寄存器文件中的)。
四、写在后面
以下纯属个人理解,如有错误或不足的地方请批评指正。
现在关于编写高效优质的代码有三种说法,一部分认为需要以内存空间换时间,因为现代计算机的内存速度、大小都有很大的进步,内存空间紧张已不再是问题;另一部分认为需要以时间换空间,因为他们任务现代计算机的计算单元的计算速度已经很快了,不需要考虑计算效率的问题;还有一部分人(那就是我,哈哈),综合前两种情况,在适当情况下,当然可以两种互换啦。