嗯,…,真的是很简单的介绍咯 @_@
CPU Cache 为何引入
-
一张比较直观的时间图:
- execute typical instruction; 1 ns;
- L1 cache reference,从 L1 cache 中取数据; 0.5 ns
- L2 cache reference,从 L2 cache 中取数据; 7 ns
- Main memory reference,从主存中取数据; 100 ns;
-
所以有必要引入 CPU Cache
CPU Cache 实现
这里真是很简单的一点点内容 @_@
- CPU Cache 模型;参考 MESI protocol.
-
CPU Cache 如何工作;
- 对于 CPU 而言,数据并没有类型,有无符号这些概念;对于 CPU 而言,这些都是一块一块的数据.
- 在 CPU 需要读取主存上的数据时,其会把该数据周围数据(一般是 Cache line 大小,64 byte)先 一同加载到 CPU Cache 中.这个应该是根据程序的局部性原理决定的.
-
Cache associativity;缓存关联性,指定了 CPU Cache 与 Memory 之间如何关联,如何映射.
-
Cache 一致性;参见MESI protocol协议.
-
原子操作与 CPU Cache;原子操作仅能保证操作具有原子性,即不会被其他 CPU 看到操作的中间状态, 在某个 CPU 执行原子操作之后其还应该按照 MESI 协议规范执行对应的内存屏障指令才能确保原子操 作的副作用被其他 CPU 可见.
- 按照我的理解,没有必要了解的太详细,只需要按照语言标准提供的内存模型来编程即可.
CPU Cache 友好的习惯
- 其实,我觉得这些亲近硬件的东西应该是编译器的锅,她应该根据源码行为以及目标平台然后生成缓存友好的 代码.
避免 false sharing
-
false sharing 是啥? 先看下面一种情景:
- 内存中的数据是以 cache line 为单位从内存中读到 CPU cache 中的,比如有两个变量 X,Y;在 内存中他们俩非常近,那么很有可能在读 X 的时候,Y 也被放到了相同 cache line 中.
- Thread1 在一个循环中不停的写 X;Thread2 不停的写 Y;那么在 Thread1 写 X 的时候,需要 Invalid 其他 cache 中对应的 cache line,Thread2 写 Y 的时候也要做同样的事情.
这样就会碰到 MESI 中 2 个耗时的操作:
- 写入到处于 Invalid 状态的 cache line.
- 将一个 cache line 的状态更改为 Invalid.
-
如何解决 false sharing;很简单,增大 X,Y 之间的距离;确保 X,Y 不会被加载到同一 Cache line 中;在 Facebook/folly 项目中, X,Y 的距离是 128 byte!
演示 false sharing
void thread_main(unsigned char *addr,const char *thread_name)
{
auto time_use = ExecuteTimeGet(7,300000000U,[&]{
unsigned char var = *addr;
++var;
*addr = var;
});
PP_QQ_LOG_D("thread: %s;time_use: %lu",thread_name,time_use);// 单位:纳秒
}
/* 若定义了 ENABLE_FALSE_SHARING,此时 2 个线程之间会 false sharing;
* 否则 2 个线程之间不会 false sharing.
*/
#if defined(ENABLE_FALSE_SHARING)
unsigned char data[2];
#else
unsigned char data[256];
#endif
int
main(int ,char **)
{
std::thread thread1 {thread_main,&data[0],"thread1"};
std::thread thread2 {thread_main,&data[sizeof(data) / sizeof(unsigned char) - 1],"thread2"};
thread1.join();
thread2.join();
return 0;
}
-
很显然,这里只是代码片段,完整见这;
-
当开启了 ENABLE_FALSE_SHARING 时的运行结果.
$ ./bin/false_sharing_test thread: thread2;time_use: 4160599200 thread: thread1;time_use: 3372905584 $ ./bin/false_sharing_test thread: thread2;time_use: 4162006784 thread: thread1;time_use: 3354563091 $ ./bin/false_sharing_test thread: thread1;time_use: 3746164897 thread: thread2;time_use: 2814324090 $ ./bin/false_sharing_test thread: thread2;time_use: 4013732999 thread: thread1;time_use: 3944717713
-
当不开启 ENABLE_FALSE_SHARING 时运行结果
$ ./bin/false_sharing_test thread: thread2;time_use: 1364076924 thread: thread1;time_use: 1375531495 $ ./bin/false_sharing_test thread: thread2;time_use: 1362867454 thread: thread1;time_use: 1373039588 $ ./bin/false_sharing_test thread: thread2;time_use: 1363529034 thread: thread1;time_use: 1374240938 $ ./bin/false_sharing_test thread: thread2;time_use: 1365391755 thread: thread1;time_use: 1369760771
可以看出,由于此时没有了 false sharing,所以运行时间还是比较大的.
参考
转载请注明出处!谢谢