经过CPU来做内存拷贝,会导致Cache污染

文章讨论了编程中数组和链表的使用,指出数组因存储连续性带来的良好局部性优于链表。链表常用于频繁增删元素的场景,但数组插入删除可能导致Cache污染。解决方案包括使用Python的deque(双端队列)减少元素移动,以及利用MMX/SSE指令进行快速无污染内存拷贝。然而,后者可能影响任务切换效率,因此仅适用于大内存块拷贝。
摘要由CSDN通过智能技术生成

在hishscalability.com上看到一篇文章:Strategy: Stop Using Linked-Lists(这文章写的挺烂,只列结论没有具体分析),正好上周看到了一篇讲同样事情的文章为什么python标准库没有链表。两篇文章主题都是让大家写代码的时候少用链表,多用数组,主要原因是locality,局部性差的后果轻则cache miss,重则page fault。数组由于存储在连续空间里,其局部性显然是好过链表。

还记得大学操作系统课上老师说过:「局部性是编程里最重要的概念之一」,当时听的一知半解,现在算是有些感觉了。

回到链表的问题。我们通常在什么情况下用链表呢?根据教科书上的大O复杂度,经常增删元素的情况下要用链表。这种场景下如果用数组替代链表,带来的overhead是数组的整体移动,按照流行的说法,内存拷贝很快,但是内存拷贝有一个被人忽略副作用:被拷贝的内存进了CPU Cache(Cache污染),数组越大,污染的越厉害。现在CPU的L1 Cache大改是4~32KB,随便一个稍微大一点的数组就可以把整个L1 Cache全部污染。Cache污染使得程序的局部性被破坏。废话几句,内存相对于CPU来说,速度非常慢,CPU速度一直保持摩尔定律,而内存速度涨速甚缓。现在服务器CPU动辄2G 16核,而内存还不到2GHz,正因为如此才有了L1/L2/L3 Cache。

如何解决数组在插入删除时的Cache污染问题呢?

 

方法一

回到具体场景,链表的增删其实大多发生在头尾;对于大规模有序数据,非头尾数据频繁增删,树是一个更合适的数据结构。 对于头尾增删这种场景,第二篇文章中提到的python的deque的实现是个不错的方法(不过原作者理解错了),deque是一个以块为基本单位的双向链表,每个块可以存62个元素,leftindex和rightindex标记头尾两个块用到哪了,这使得插入头尾的增删不需要任何元素移动。有兴趣的可以读一下源码。

PYTHON DEQUE

方法二

从另一个角度来考虑,能不能在内存复制的时候,避免cache污染呢?一个数据想进CPU寄存器,肯定会进Cache,这岂不是没招了。Google了一下,发现还是有办法的,不进CPU寄存器,还可以进FPU的寄存器(MMX寄存器),利用MMX和SSE指令完成内存拷贝。如下代码,不但完全不经过CPU Cache,而且使用了SIMD技术,每次可以拷贝16字节。

void memcpy(char * dst, char * src, unsigned size) {
    char * dst_end = dst + size;
    while (dst != dst_end) {
        __m128i res = _mm_stream_load_si128((__m128i *)src);
        *((__m128i *)dst) = res;
        src += 16;
        dst += 16;
    }
}

注1:引自Stack Overflow:How to use movntdqa to avoid cache pollution
注2:_mm_stream_load_si128是vs的私有函数,对SSE4的movntdqa的封装,MSDN链接

这个版本的memcpy又快又不污染cache,为什么没有成为c库或者kernel的实现呢?早在2000年Linus就和别人讨论过这个问题。Linus回复的大意如下:FPU的寄存器当然不是免费用的,每次使用都得保存现场恢复现场。因为FPU平时很少用,所以linux中有一个lasy FP switching的机制,加快task switch。如果memcpy使用FPU寄存器,这个机制基本就失效了,从而使得task switching变慢,这是一个可怕的副作用。但是Linus也没把这个优化一棍子拍死:「如果你非要用,只在拷贝内存区比较大的情况下使用这个优化,小于几K就算了」。贴上Linus的回复,完整的讨论见:Page Zeroing Strategy

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值