星期一, 五月 16, 2011

What every programmer should know about memory 阅读笔记(6.4)

http://lwn.net/Articles/256433/

6.4 Multi-Thread Optimizations
三个方面比较重要 一致性 原子性 带宽


6.4.1 Concurrency Optimizations
为了保持cache一致性 不同芯片的多线程写同一地址会效率差 单个芯片多core因为cache共享就会好很多

优化一致性的问题的核心思路是将一致性要求不同的变量放入不同的cache line

优化方法
方法一 读写变量和初始化后只读变量分入不同section(gcc对const变量会自动这么做) 使他们在内存和cache上分开 如果可以
还建议将读多写少变量分入单独section

方法二 将经常被同时访问的变量放入同一structure确保他们在内存结构上在一起

方法三 将经常被不同线程写的变量放入同一structure确保他们在内存结构上在一起

方法二 方法三可配合__attribute__((aligned(l1 cache line size)))确保全部变量在一个cache line

方法四 如果一个变量被多个线程分别使用 并且无一致性需求 可用__thread将变量放入tls


6.4.2 Atomicity Optimizations
主要介绍原子内存操作


6.4.3 Bandwidth Considerations
每个芯片连接内存都有一个最大带宽 而其会被芯片中的core和硬线程共享
多个芯片连接内存的方式可能也是共享的
所以效率也受到带宽的影响

比较新机器的performance measurement counters可以探测这个问题

方法一 买更快的机器 在带宽问题只能在部分机器出现 且换新机器比重写问题程序便宜的时候 这是个可用的方法

方法二 如果几个线程经常访问同样的数据 将同一进程的几个线程放在有cache共享的几个core上

方法三 如果几个线程经常访问不同的数据 不要将他们放入同一个core(我认为cache共享的几个core上) 否则易引起cache冲突

文中介绍了一些控制线程在哪个core上运行的接口

方法二 方法三是有冲突的 所以使用时务必分析好当前面对的问题 并作实验

星期三, 五月 11, 2011

What every programmer should know about memory 阅读笔记(6.4.1)

http://lwn.net/Articles/256433/
6.4 Multi-Thread Optimizations
三个方面比较重要 一致性 原子性 带宽

6.4.1 Concurrency Optimizations
为了保持cache一致性 不同芯片的多线程写同一地址会效率差 单个芯片多core因为cache共享就会好很多

优化一致性的问题的核心思路是将一致性要求不同的变量放入不同的cache line

优化方法
方法一 读写变量和初始化后只读变量分入不同section(gcc对const变量会自动这么做) 使他们在内存和cache上分开 如果可以
还建议将读多写少变量分入单独section

方法二 将经常被同时访问的变量放入同一structure确保他们在内存结构上在一起

方法三 将经常被不同线程写的变量放入同一structure确保他们在内存结构上在一起

注意:方法二 方法三可配合__attribute__((aligned(l1 cache line size)))强制其和l1 cache
line对齐确保全部变量在一个cache line

方法四 如果一个变量被多个线程分别使用 并且无一致性需求 可用__thread将变量放入tls

星期四, 五月 05, 2011

What every programmer should know about memory 阅读笔记(6.3)

http://lwn.net/Articles/255364/
6.3 Prefetching
6.3.1 Hardware Prefetching
两次或者更多次的cache miss才会引起prefetch 因为CPU有随机访问引起cache miss的情况 比如访问一个全局变量
如果这样也prefetch会影响效率
一个CPU中会有多个prefetch unit进行prefetch 高级的cache的unit可能多个core共享
prefetch不能越过一个页 因为可能引起page fault或者fetch一个并不需要的页
在不需要的时候引起prefetch 需要调整程序结构才能解决 在指令中插入未定义指令是一种解决方法
体系结构提供全部或者部分关闭prefetch


6.3.2 Software Prefetching
#include <xmmintrin.h>
enum _mm_hint
{
_MM_HINT_T0 = 3,
_MM_HINT_T1 = 2,
_MM_HINT_T2 = 1,
_MM_HINT_NTA = 0
};
void _mm_prefetch(void *p, enum _mm_hint h);
x86系列可用上面函数生成prefetch指令
_MM_HINT_T0 = 3, _MM_HINT_T1 = 2, _MM_HINT_T2 = 1, 就是将内存取到1 2 3级cache
要注意第一每个芯片可能对其实现不同 第二 一般使用MM_HINT_T0 但是如果数据量较大可用另两个选项
_MM_HINT_NTA的NTA是non-temporal access(原文在这里写错了)的缩写 当使用这个选项的时候 数据将被装入l1
但是当这个cache要装入其他数据的时候 原来的数据将不会被存入更高级别的cache 如果有需要数据还要直接写入内存
注意如果数据量很大不要使用这个选项
AMD的CPU提供一种特殊的prefetch指令
直接在程序中增加prefetch代码可能起到作用不大 建议方法是用performance counters查询程序的cache
misses信息 在需要的位置插入prefetch指令
-fprefetch-loop-arrays是GCC提供的编译选项 其将为优化数组循环插入prefetch指令 但是这个选项要小心使用


6.3.3 Special Kind of Prefetch: Speculation
介绍了IA64下提高乱序执行的Speculative loads
其基本思路是当一个load指令和后面的指令关联 造成无法OOO的时候 将load换成Speculative loads
其在有关联的时候不会生效 这样即使指令关联也可OOO


6.3.4 Helper Threads
在当前程序中同时做prefetch会增加程序复杂度 而且可能引起l1i的性能问题
可以单独创建一个线程做prefetch 这个线程要跟执行线程在同一core不同的硬线程 这样他们的l2 cache就是共享的
cpu_set_t self; NUMA_cpu_self_current_mask(sizeof(self), &self);
cpu_set_t hts; NUMA_cpu_level_mask(sizeof(hts), &hts, sizeof(self),
&self, 1); CPU_XOR(&hts, &hts, &self);
这个函数可用来取得cache共享的信息


6.3.5 Direct Cache Access
有一种cache miss是由输入数据造成的 现在的网卡等设备为了提高输入数据的速度都支DMA 数据会不通过CPU直接写入内存
但是这个方式有个问题 有些输入的数据很快就会被CPU处理 比如网卡接到的数据需要被分析类型 这就会产生cache miss影响处理速度
所以INTEL在他们的芯片中增加了DCA功能 其扩展了网卡到内存控制器的协议 在其中传输数据的时候可以增加DCA标志
当CPU从FSB中看到有DCA标志的数据的时候 其将把这个数据写入CACHE(当然CPU也可以根据情况乎略DCA)
这样就减少了之后CPU处理数据时候的cache miss