CPU性能优化--后端优化
前面已经讨论过CPU后端组件,CPU后端低效往往被描述为这样一种场景,当前端已经完成取指令和译码后,后端发生了过载而不能处理新的指令。从技术上讲,这是一种由于后端缺少资源无法处理新的微操作而导致前端不能交付微操作的场景。例如,数据缓存未命中导致的停滞以及除法单元过载导致的停滞。
需要强调的是,只有在TMA显示高 后端绑定指标时,才需要考虑针对CPU后端进行代码优化。TMA进一步把后端绑定指标拆解为两个类别;内存绑定 和核心绑定,接下来,我们将讨论这两个类别。
8.1 内存绑定
当应用程序执行大量的内存访问并且花比较长的时间等待内存访问完成时,那么该应用程序会被表征为内存绑定。这意味着要提高程序的性能,我们可能需要改善程序的内存访问情况,减少内存访问次数或者升级内存子系统。
图45佐证了内存的分层性能非常重要这一说法。该图展示了内存和处理器的性能差距,纵轴以对数刻度显示了CPU和DRAM性能差距的增长趋势,内存基线时从1980年的64KB DRAM芯片的内存访问延迟开始的。典型的DRAM性能每年提升7%,而CPU每年提升20-50%。
在TMA中,内存绑定会统计CPU流水线由于按需加载或存储指令而阻塞的部分槽位。解决此类性能问题的第一步是,定位导致高内存绑定 指标的内存访问,识别出导致问题的具体内存访问后,就可以通过集中策略来进行优化,下面,我们将讨论几个典型的案例。
8.1.1 缓存友好的数据结构
从缓存中读取一个变量只会花费几个时钟周期,但是,如果变量不在缓存中而从RAM内存中读取的话,就会花费100多个时钟周期。关于编写缓存友好算法和数据结构的重要信息还有很多,因为这是高性能应用程序的关键要素之一。编写缓存友好代码的关键是时间和空间局部性原则,目标是从缓存中高效的读取所需要的数据,在设计缓存友好的代码时,从缓存行角度考虑是很有意义的,不仅仅是考虑单个变量和他们在内存中的位置。
8.1.1.1 按顺序访问数据
利用缓存空间局部性的最佳方法是按顺序访问内存,这样做,我们可以利用硬件预取器识别内存访问模式,并提前引入下一个数据块。代码清单19展示了一个C代码的例子,实现了缓存友好访问。因为它按元素在内存中的排列顺序访问矩阵的元素,所以它是缓存友好的。如果交换数组中索引的顺序,将导致列优先遍历,这样的话就没有利用空间局部性,会损害性能。
缓存友好的内存访问
for (row = 0; row < NUMROWS; row++)
for (column = 0; column < NUMCOLUMNS; column++)
matrix[row][column] = row + column;
8.1.1.2 使用适当的容器
几乎在任何语言中都有各种个样的现成容器,了解它们的底层存储机制和性能影响很重要。
此外,还要考虑代码将如何处理数据,然后才能选择数据存储的方式,当对象很大时,是将对象存储的数组中,还是存储指向这些对象的指针?指针数组占用的内存更少,有利于修改数组的操作。
8.1.13 打包数据
通过使用数据更紧密来提高内存层次利用率,
struct S
{
unsigned a;
unsigned b;
unsigned c;
};
3 * 4个字节。
struct S
{
unsigned a:4;
unsigned b:2;
unsigned c:2;
};
8.1.1.4 对齐与填充
对象的起始地址16字节对齐
alignas(16) int16_t a[N];
#define CACHELINE_ALIGN alignas(64)
struct CACHELINE_ALIGN S {
}
对齐可能导致未使用的字节出现空位,可能会降低内存带宽利用率。如果上面的例子中结构体S只有40B,S的下一个对象于下一个缓存行的开头开始,每个保存S对象的缓存行都会留下64 -40 = 24未使用的字节。
我们需要填充数据结构成员以避免边缘情况,例如缓存争用。例如,在多线程应用程序中,当两个线程A和B访问同一结构的不同字段时,可能会出现共享问题。
对齐注意事项中的最重要的一个是SIMD代码,当 依赖编译器自东向量化,开发者不需要做任何特殊的事情,然而,当使用编译器向量化内建函数编写代码时,需要地址能被16,32,64整除。编译器内部头文件提供的向量类型已经被注释,以确保进行适当的对齐。
__m512 *pre = new __m512[N];
8.1.1.5 动态内存分配
利用自定义malloc分配器,提高效率。
jemalloc和tcmalloc。
8.1.1.6 针对内存存储层次调优代码
某些应用程序的性能取决于特定层缓存的大小,最著名的例子是使用循环分块来改进矩阵乘法。思想是将矩阵的工作区域分散成更小的块。这样每个块都能适合L2缓存。
8.1.2 显示内存预取
__builtin_prefetch手动显示的添加预取指令
要使预取生效,请务必提前插入预取提示,确保被加载的值在被用于计算时已经在缓存中。也不要过早的插入预期提示,可能会预取那段时间内用不到的数据。
原文地址:https://blog.csdn.net/fantasy_ARM9/article/details/144729876
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!