大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > C技巧 > 充分利用CPU高速缓存,提高程序效率(原理篇)

充分利用CPU高速缓存,提高程序效率(原理篇)(2)

关键词:高速缓存效率  阅读(3190) 赞(20)

[摘要]本文是对充分利用CPU高速缓存,提高程序效率(原理篇)的讲解,与大家分享。

4. False Sharing(伪共享)问题

到现在为止,我们似乎还没有提到cache如何和内存保持一致的问题。

其实在cacheline中,还有其他的标志位,其中一个用于标记cacheline是否被写过。我们称为modified位。当modified=1时,表明cacheline被CPU写过。这说明,该cacheline中的内容可能已经被CPU修改过了,这样就与内存中相应的那些存储单元不一致了。因此,如果cacheline被写过,那么我们就应该将该cacheline中的内容写回到内存中,以保持数据的一致性。

现在问题来了,我们什么时候写回到内存中呢?当然不会是每当modified位被置1就写,这样会极大降低cache的性能,因为每次都要进行内存读写操作。事实上,大多数系统都会在这样的情况下将cacheline中的内容写回到内存:

当该cacheline被置换出去时,且modified位为1。

现在大多数系统正从单处理器环境慢慢过渡到多处理器环境。一个机器中集成2个,4个,甚至是16个CPU。那么新的问题来了。

以Intel处理器为典型代表,L1级cache是CPU专有的。

先看以下例子:

系统是双核的,即为有2个CPU,CPU(例如Intel Pentium处理器)L1 cache是专有的,对于其他CPU不可见,每个cacheline有8个储存单元。

我们的程序中,有一个 char arr[8] 的数组,这个数组当然会被映射到CPU L1 cache中相同的cacheline,因为映射机制是硬件实现的,相同的内存都会被映射到同一个cacheline。

2个线程分别对这个数组进行写操作。当0号线程和1号线程分别运行于0号CPU和1号CPU时,假设运行序列如下:

开始CPU 0对arr[0]写;

随后CPU 1对arr[1]写;

随后CPU 0对arr[2]写;

……

CPU 1对arr[7]写;

根据多处理器中cache一致性的协议:

当CPU 0对arr[0]写时,8个char单元的数组被加载到CPU 0的某一个cacheline中,该cacheline的modified位已经被置1了;

当CPU 1对arr[1]写时,该数组应该也被加载到CPU 1的某个cacheline中,但是该数组在cpu0的cache中已经被改变,所以cpu0首先将cacheline中的内容写回到内存,然后再从内存中加载该数组到CPU 1中的cacheline中。CPU 1的写操作会让CPU 0对应的cacheline变为invalid状态注意,由于相同的映射机制,cpu1 中的 cacheline 和cpu0 中的cacheline在逻辑上是同一行(直接映射机制中是同一行,组相联映射中则是同一组)

当CPU 0对arr[2]写时,该cacheline是invalid状态,故CPU 1需要将cacheline中的数组数据传送给CPU 0,CPU 0在对其cacheline写时,又会将CPU 1中相应的cacheline置为invalid状态

……

如此往复,cache的性能遭到了极大的损伤!此程序在多核处理器下的性能还不如在单核处理器下的性能高。

多CPU同时访问同一块内存区域就是“共享”,就会产生冲突,需要控制协议来协调访问。会引起“共享”的最小内存区域大小就是一个cache line。因此,当两个以上CPU都要访问同一个cache line大小的内存区域时,就会引起冲突,这种情况就叫“共享”。但是,这种情况里面又包含了“其实不是共享”的“伪共享”情况。比如,两个处理器各要访问一个word,这两个word却存在于同一个cache line大小的区域里,这时,从应用逻辑层面说,这两个处理器并没有共享内存,因为他们访问的是不同的内容(不同的word)。但是因为cache line的存在和限制,这两个CPU要访问这两个不同的word时,却一定要访问同一个cache line块,产生了事实上的“共享”。显然,由于cache line大小限制带来的这种“伪共享”是我们不想要的,会浪费系统资源(此段摘自如下网址:http://software.intel.com/zh-cn/blogs/2010/02/26/false-sharing/)

对于伪共享问题,有2种比较好的方法:

1. 增大数组元素的间隔使得由不同线程存取的元素位于不同的cache line上。典型的空间换时间
2. 在每个线程中创建全局数组各个元素的本地拷贝,然后结束后再写回全局数组

而我们要说的linux cache机制,就与第1种方法有关。

«上一页12下一页»


相关评论