CPU缓存,cache line 和 False Sharing 简介
2021/12/13 6:23:22
本文主要是介绍CPU缓存,cache line 和 False Sharing 简介,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
- Cache 基础知识
目前,一般的CPU会有三级内存(L1,L2,L3 ),如下图所示。
其中:
- L1 缓存分成两种,一种是指令缓存,另一种是数据缓存。L2 和 L3 缓存不分指令和数据。
- L1 和 L2 缓存在每一个 CPU 核中,L3 则是所有 CPU 核心共享的内存。
- L1、L2、L3 的离CPU越近速度越快,但也越小;离 CPU越 远速度越慢,也越大。
再往后就是内存,内存的后面就是硬盘。
存取速度如下:
- L1 的存取速度:4 个CPU时钟周期
- L2 的存取速度:11 个CPU时钟周期
- L3 的存取速度:39 个CPU时钟周期
- RAM内存的存取速度 :107 个CPU时钟周期
建立这么多级的缓存,会引入2个比较重要的问题:
- 一是缓存命中率问题
- 二是缓存更新的一致性问题
尤其是第二个问题,在多核技术下,就很像分布式的系统,要对多个地方进行更新。
例如:Intel Core i7-8700K ,是一个 6 核的 CPU,每核上的 L1 是 64KB(数据和指令各 32KB),L2 是 256K,L3 有 2MB
- 术语: Cache Line
CPU每次加载一块数据,这就是 cache line.
主流的 CPU 的 Cache Line 是 64 Bytes(也有的CPU用 32 和 128 Bytes),这就是 CPU 从内存中捞数据上来的最小数据单位。
因为Cache Line是最小单位(64Bytes),所以先把 Cache 分布多个 Cache Line,比如:L1 有 32KB,那么,32KB/64B = 512 个 Cache Line。
- 缓存的一致性 与 False Sharing
如果有一个数据 x 在 CPU 第 0 核的缓存上被更新了,那么其它 CPU 核上对于这个数据 x 的值也要被更新,这就是缓存一致性的问题。(这对上层的代码是透明的)
来看一个程序。在这个程序中,有一个数组,拥有 16M 个元素,每个元素的值都是随机的。我们用这个程序来统计整个数组包含多少个奇数。
程序中总共列了3种方法,并统计了每种方法的执行时间。具体如下:
#include <chrono> #include <iostream> #include <thread> #include <vector> using namespace std; const int total_size = 16*1024*1024; int * test_data = new int [total_size]; const int thread_num = 6; int result[thread_num]; void thread_func_for_multi_thread(int id) { result[id] = 0; int chunk_size = total_size / thread_num + 1; int start = id * chunk_size; int end = min(start + chunk_size, total_size); for (int i=start; i<end; i++) { if (test_data[i] % 2) result[id] ++; } } void thread_func_for_multi_thread_improved(int id) { int count = 0; int chunk_size = total_size / thread_num + 1; int start = id * chunk_size; int end = min(start + chunk_size, total_size); for (int i=start; i<end; i++) { if (test_data[i] % 2) count ++; } result[id] = count; } void thread_func_for_one_thread() { int count = 0; for (int i=0; i<total_size; i++) { if (test_data[i] % 2) count ++; } cout << "One thread: count = " << count << endl; } void test() { // init test_data array for (int i=0; i<total_size; i++) test_data[i] = rand(); for (int i=0; i<thread_num; i++) result[i] = 0; // 方法-1: 多线程处理 vector<thread> vec_thr; auto beg = std::chrono::system_clock::now(); for (int i=0; i<thread_num; i++) { vec_thr.emplace_back(thread_func_for_multi_thread, i); } for (int i=0; i<thread_num; i++) { vec_thr[i].join(); } int sum = 0; for (int i=0; i<thread_num; i++) { sum += result[i]; } cout << "sum = " << sum << endl; auto end = std::chrono::system_clock::now(); auto time_cost = std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count(); cout << "Elapsed time: " << time_cost << " ms" << endl; // 方法-2: 单线程处理 beg = std::chrono::system_clock::now(); thread one_thrd(thread_func_for_one_thread); one_thrd.join(); end = std::chrono::system_clock::now(); time_cost = std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count(); cout << "Elapsed time: " << time_cost << " ms" << endl; // 方法-3: 多线程处理,避开 False Sharing vector<thread> vec_thr_2; beg = std::chrono::system_clock::now(); for (int i=0; i<thread_num; i++) { vec_thr_2.emplace_back(thread_func_for_multi_thread_improved, i); } for (int i=0; i<thread_num; i++) { vec_thr_2[i].join(); } sum = 0; for (int i=0; i<thread_num; i++) { sum += result[i]; } cout << "Improved Multi Thread: sum = " << sum << endl; end = std::chrono::system_clock::now(); time_cost = std::chrono::duration_cast<std::chrono::milliseconds>(end - beg).count(); cout << "Elapsed time: " << time_cost << " ms" << endl; } int main() { test(); return 0; }
运行结果如下:
$g++ 2.cpp -lpthread -O3 $./a.out sum = 8387609 Elapsed time: 27 ms One thread: count = 8387609 Elapsed time: 6 ms Improved Multi Thread: sum = 8387609 Elapsed time: 1 ms
由上可见:
方法-1是多线程的方法,耗时30ms;
方法-2是单线程,耗时11ms;
方法-3还是多线程,耗时仅 2ms;
为什么方法-1的多线程比方法-2的单线程还慢呢?
因为 result[] 这个数组中的数据存在同一个 Cache Line 中,而所有的线程都会对这个 Cache Line 进行写操作,从而导致所有的线程都在不断地重新同步 result[] 所在的 Cache Line,因此 12 个线程还跑不过一个线程。这叫 False Sharing
那么方法-3相比方法-1,做的改进是什么呢?
就是用一个本地变量来记录count值,最后结束的时候再赋给 result[i]. 这样,就避免了所有线程不断更新同一个cache line了。
这个程序对于多线程编程中的 Performance 提升还是有相当借鉴意义的。值得记录下来。
参考文献:
CPU Cache
(完)
这篇关于CPU缓存,cache line 和 False Sharing 简介的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-25安卓NDK 是什么?-icode9专业技术文章分享
- 2024-12-25caddy 可以定义日志到 文件吗?-icode9专业技术文章分享
- 2024-12-25wordfence如何设置密码规则?-icode9专业技术文章分享
- 2024-12-25有哪些方法可以实现 DLL 文件路径的管理?-icode9专业技术文章分享
- 2024-12-25错误信息 "At least one element in the source array could not be cast down to the destination array-icode9专业技术文章分享
- 2024-12-25'flutter' 不是内部或外部命令,也不是可运行的程序 或批处理文件。错误信息提示什么意思?-icode9专业技术文章分享
- 2024-12-25flutter项目 as提示Cannot resolve symbol 'embedding'提示什么意思?-icode9专业技术文章分享
- 2024-12-24怎么切换 Git 项目的远程仓库地址?-icode9专业技术文章分享
- 2024-12-24怎么更改 Git 远程仓库的名称?-icode9专业技术文章分享
- 2024-12-24更改 Git 本地分支关联的远程分支是什么命令?-icode9专业技术文章分享