原文地址:
11 多维数组访问方式
多维数组遍历或索引时,内层循环在外层循环内迭代。其实这个大家应该都知道。但是还是说一说。这个其实主要是高速缓存有效使用的问题。
这里有一个概念:数据时间密度和数据空间密度。一个时间段内多次访问同一数据,数据时间密度就高,也就是说一旦数据被取到cache,就尽可能的长时间使用。空间密度是指,cache大小有限 ,每次取数据到cache中时,尽可能多的拿到接下来操作会用到的所有数据。如cache line大小为64 bytes,每次从内存读到cache的数据块大小就也是64 bytes。例如,某时刻cpu需要循环操作一系列数据,发现当前cache中没有要操作的数据,于是cache未命中,然后到其它缓存或内存中找到数据,把包括需要的数据以及它周围的64 bytes存放到最接近cpu的cache中。然后循环操作,如果这64bytes内都是接下来循环要操作的数据,就不用来来回回的再次搬运了。如果每次循环都需要到外面去拿数据,那cpu只能操作一次,等待读取....那你的速度可想而知多慢了吧。
这里说的二维数据的访问方式主要说的就是这个。如 int a[maxx][maxy],数组在内存存放的方式是按照一维存放的,从前到后,从上到下的顺序一致排开,也就是说下面数组的第一行最后一个元素紧挨着第二行的第一个,第二行的最后一个元素挨着第三行的第一个,逐次类推:
a[0][0],a[0][1],a[0][2].....a[0][maxy-2],a[0][maxy-1]
a[1][0],a[1][1],a[1][2].....a[1][maxy-2],a[1][maxy-1]
....
a[maxx-1][0],a[maxx-1][1]..... a[maxx-1][maxy-1]
如果访问按照在内存当中存放的顺序访问,也就是行优先访问,那么读取到缓存的数据就是前后紧挨的,同样cpu需要的也正是这些数据。可以这么理解,当你需要a[0][0]时,cpu可能会把第一行全部读取到cache中,那解接下来的循环所需要的数据就不用去外边取了。如果你按列访问的话,由于在内存存放是按照行优先顺序存放的,同样当你需要a[0][0]数据时,cpu同样把一行数据读取到cache中,但是cpu真正需要的只有第一个能用,其它都无效,因为cpu需要的第二个数据是a[1][0],cache未命中,那就只能再跑一趟。你跑来跑去的,还能干啥活,所以效率就很低。
12 与char和short相比较,尽量使用int
其背后的主要原因是C和C++在整数级别执行算术运算和参数传递。 即时使用的值用一个字节比较合适,仍应该考虑使用int类型。 如果使用char,编译器将首先将值转换为int,执行操作,然后将结果转换回char。让我们考虑以下代码,该代码提供了两个对char和int执行相同操作的函数。
-
char sum_char(char a, char b)
-
{
-
char c;
-
c = a + b;
-
return c;
-
}
-
-
int sum_int(int a, int b)
-
{
-
int c;
-
c = a + b;
-
return c;
-
}
对sum_char调用涉及以下操作:
通过符号扩展将第二个参数b转换为int型(C和C++参数压栈从右向左);
将符号扩展后的参数int b压入堆栈;
通过符号扩展将第一个参数a转换为int;
将符号扩展后的参数int a压入堆栈;
被调用的函数执行a+b;
结果被强制转换为char;
结果存储在char c中;
c再次符号扩展;
将扩展后的c复制到返回值寄存器中,然后返回到调用函数;
现在,调用函数再次从int转换为char;
结果被存储。
对sum_int调用涉及以下操作:
将int b压入堆栈;
将int a压入堆栈;
调用函数执行a+b;
结果存储在int c中;
c被复制到返回值寄存器中,返回到调用函数;
存储返回的值。
因此,我们可以得出结论,除非存储要求迫使我们使用char或short,否则应该尽量使用整数变量。当必须使用char和short时,请考虑字节对齐和排序的影响,以查看是否真正节省了空间。
13 定义轻量级构造函数
保持构造函数轻巧。 每次创建对象时都会调用该构造函数。 请记住,很多时候,编译器可能会在你意想不到或忽略的地方创建临时对象。因此,优化构造函数可能会大大提高性能。 如果有对象数组尤其要注意 ,应首先优化对象的默认构造函数,因为数组中的每个对象都会调用该构造函数。
14 初始化优于赋值,尽量使用初始化列表
赋值操作至少多一次对象创建过程。使用初始化列表也是同理。
15 不要随意声明虚函数,除非必须
虚拟函数调用比常规函数调用昂贵,因此不要“以防以后万一”有人需要覆盖默认行为而使函数虚拟化。 如果需要,开发人员也可以编辑其他基类头文件以将声明更改为virtual。
至于虚函数为何调用成本高,C++很不经常使用,我不是十分清楚。但是估计查表也不至于太浪费时间吧,还有可能会影响指令并行吧。对C++比较熟悉的可以发表一下意见。
16 小函数内联
将小型函数(1到3行)转换为内联处理将大大提高吞吐量。 内联将消除函数调用和关联参数传递的开销。 但是由于相关的代码膨胀,将此技术用于较大的功能可能会对性能产生负面影响。这个就是说就那么几行代码,与执行成本相比较,调用成本更高,那还为毛非得写成函数呢,直接内联。
17 避免循环内执行函数调用
这个大家都懂。最经常遇见的可能就是strlen()函数了。如for(i = 0; i < strlen(str); i++)或while(getObject().status).这些循环内的函数调用会被循环次数放大而导致性能下降。
18 使用前增而不是后增
我的理解就是尽量使用类似++i,而不是i++的操作。在c++的对象实例操作过程中,后者多一次临时变量创建的过程。
19 在c++11中使用 move构造函数
这一段,我看不太明白,不做处理。因该是C++03老是执行复制构造函数,对于临时变量很是浪费,这一点在c++11中得到改进,叫move构造函数。move构造里,不分配空间,只是copy地址指针。不知道理解对不对。原文地址:底部。
20 使用硬件加速器和SIMD硬件
对编译器选项进行彻底的梳理。 检查是否启用了新的硬件以及性能和并行化选项。 例如,英特尔处理器支持256位SIMD单元AVX。 英特尔/ AMD处理器还支持SSE 4.2。如果您的平台配备了GPU,则可以使用CUDA(NVIDIA)或C ++ AMP(Microsoft)融合到并行化的算法以改善自身性能。 当没有可用的GPU时,C ++ AMP也可用于提高性能。
21 使用配置文件优化
Microsoft和Intel的编译器支持按配置优化(POGO)。 借助POGO,编译器可以根据实际程序执行中的信息来查找针对速度或性能方面需要进行优化的区域。 编译器还可以使用先前运行的历史记录来生成循环和分支的最佳代码。
阅读(1101) | 评论(0) | 转发(0) |