沈天真
作者沈天真·2021-04-05 14:08
售前支持·IPS

你真的了解CPU Cache吗?系列----编写cache友好的代码

字数 1502阅读 2548评论 0赞 0

本文是系列文章,建议您先阅读

你真的了解CPU Cache吗?系列----基础知识

https://www.talkwithtrend.com/Article/254441

你真的了解CPU Cache吗?系列----基础知识II

https://www.talkwithtrend.com/Article/254465

为了理解cache性能,除了基础知识文章中提到的cache相关概念,您必须掌握如下提到的概念。

内存延迟(memory latency):CPU访问内存,内存返回所需数据的时间

内存带宽(memory bandwidth):CPU和内存之间拷贝数据的通道大小

内存页(page):内存页是个虚拟概念,将内存分成固定小的块,每一块就是一页,x86上通常的页大小是4KB。

内存页表(page table):内存页表保存了从虚拟地址到实际物理地址的映射关系。

TLB(Translation Lookaside Buffer): 保存了最近使用的页表的映射,可以看做页表的cache。

预取(prefetching):CPU猜测下一个需要的内存数据,并提前load到cache中。如果猜对了,会提升性能。如果猜错了,会降低性能。

关于cache性能的几个原则

内存中的某个数据被访问了,那么它很可能会被再次访问。

内存中的某个数据被访问了,那么和它地址相邻的数据也很可能会被访问。

如果有大量的cache miss,则很容易导致系统性能问题

如果有系统性能问题,最好检查一下cache hit和cache miss的比例

顺序访问内存是性能最佳的,可以发挥预取的优势。

完全随机访问内存是性能最差的,预取不会有用,TLB也不会有用。-

编写cache友好的代码

在结构定义时,将经常使用的数据定义在一起,这样,经常使用的数据会长时间在cache中。

将经常使用的数据和不常使用的数据分在不同的结构中。

例如:

好的示例:

struct good

{

int often_used1;

int often_used2;

}

struct good2

{

int not_often_used1;

int not_often_used2;

}

不好的示例:

struct bad

{

int often_used1;

int often_used2;

int not_often_used1;

int not_often_used2;

}

尽量减少小内存块的分配

小内存块的分配,会导致随机访问增多。另外,也会造成内存碎片增多。

考虑字节对齐

结构定义时,尽量将大小相同的数据类型放在一起,按照顺序从大到小排列。

下面是常见数据类型的字节对齐规则。

如果不注意字节对齐,会造成内存空洞。

尽量以行的顺序来访问数据,这样会尽量的利用cache line。

尽量避免一次分析太大的内存

上面的循环中,如果 bigsize 特别大,那么每次迭代都会重新load数据到cache line。如果改为下面的写法,就要好很多。

这样,在j从0到10的迭代时,cache line 不需要重新load。

伪共享 (False Sharing)

现代CPU都已经是多核多线程的结构,伪共享也成为常见的由于多线程引起的cache性能问题。当本来被不同线程所有的应该互相独立的数据,被存放于同一个cache line中时,很容易出现伪共享的情况,即一个线程修改自己的数据时,顺带着就将另一个线程拥有的数据也给置为无效了。

避免伪共享的方法,可以采用Padding的方法,将结构体的大小变成和cache line一样大,这样CPU就不会将他们load到同一个cache Line中,如上图中的Sum[0]和Sum[2]。

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

0

添加新评论0 条评论

Ctrl+Enter 发表

作者其他文章

相关文章

相关问题

相关资料

X社区推广