前阵子接触到一道关于数组内部链表(多用于内存池技术)的数据结构的题, 这种数据结构能够比普通链表在cache中更容易命中, 理由很简单, 就是因为其在地址上是连续的(=.=!), 借这个机会, 就对cpu cache进行了一个研究, 今天做一个简单的分享, 首先先来普及一下cpu cache的知识, 这里的cache是指cpu的高速缓存. 在我们程序员看来, 缓存是一个透明部件. 因此, 程序员通常无法直接干预对缓存的操作. 但是, 确实可以根据缓存的特点对程序代码实施特定优化, 从而更好地利用高速缓存.
CPU1读取了数据a(假设a小于cache行大小),并存入CPU1的高速缓存.
CPU2也读取了数据a,并存入CPU2的高速缓存.
CPU1修改了数据a, a被放回CPU1的高速缓存行. 但是该信息并没有被写入RAM.
CPU2访问a, 但由于CPU1并未将数据写入RAM, 导致了数据不同步.
为了解决这个问题, 芯片设计者制定了一个规则. 当一个CPU修改高速缓存行中的字节时, 计算机中的其它CPU会被通知, 它们的高速缓存将视为无效. 于是, 在上面的情况下, CPU2发现自己的高速缓存中数据已无效, CPU1将立即把自己的数据写回RAM, 然后CPU2重新读取该数据. 这样就完成了一次两个cpu之间cache的同步.
为了测试上述场景, 我编写了如下程序进行测试:
1 #define EXEC_COUNT (100 * 1000 * 1000) 2 3 struct bits_t 4 { 5 int a; 6 char placeholder[64]; 7 int b; 8 }; 9 10 struct bits_t bits; 11 12 int which_cpu(const char* prefix_) 13 { 14 #ifdef ENABLE_WHCIH_CPU 15 cpu_set_t cur_cpu; 16 CPU_ZERO(&cur_cpu); 17 if (sched_getaffinity(0, sizeof(cur_cpu), &cur_cpu) == -1) 18 { 19 printf("warning: cound not get cpu affinity, continuing...\n"); 20 return -1; 21 } 22 int num = sysconf(_SC_NPROCESSORS_CONF); 23 for (int i = 0; i < num; i++) 24 { 25 if (CPU_ISSET(i, &cur_cpu)) 26 { 27 printf("[%s] this process %d is running processor : %d\n", prefix_, getpid(), i); 28 } 29 } 30 #endif 31 32 return 0; 33 } 34 35 int set_cpu(int cpu_id_) 36 { 37 #ifdef ENABLE_SET_CPU 38 cpu_set_t mask; 39 CPU_ZERO(&mask); 40 CPU_SET(cpu_id_, &mask); 41 if (sched_setaffinity(0, sizeof(mask), &mask) == -1) 42 { 43 printf("warning: could not set CPU affinity, continuing...\n"); 44 return -1; 45 } 46 #endif 47 48 return 0; 49 } 50 51 void* thd_func1(void* arg_) 52 { 53 set_cpu(0); 54 which_cpu("thread 1 start"); 55 timeval begin_tv; 56 gettimeofday(&begin_tv, NULL); 57 58 for (int i = 0; i < EXEC_COUNT; i++) 59 { 60 bits.a += 1; 61 int a = bits.a; 62 } 63 64 timeval end_tv; 65 gettimeofday(&end_tv, NULL); 66 printf("thd1 perf:[%lu]us\n", (end_tv.tv_sec * 1000 * 1000 + end_tv.tv_usec) - (begin_tv.tv_sec * 1000 * 1000 + begin_tv.tv_usec)); 67 which_cpu("thread 1 end"); 68 69 return NULL; 70 } 71 72 void* thd_func2(void* arg_) 73 { 74 set_cpu(1); 75 which_cpu("thread 2 start"); 76 timeval begin_tv; 77 gettimeofday(&begin_tv, NULL); 78 79 for (int i = 0; i < EXEC_COUNT; i++) 80 { 81 bits.b += 2; 82 int b = bits.b; 83 } 84 85 timeval end_tv; 86 gettimeofday(&end_tv, NULL); 87 printf("thd2 perf:[%lu]us\n", (end_tv.tv_sec * 1000 * 1000 + end_tv.tv_usec) - (begin_tv.tv_sec * 1000 * 1000 + begin_tv.tv_usec)); 88 which_cpu("thread 2 end"); 89 90 return NULL; 91 } 92 93 int main(int argc_, char* argv_[]) 94 { 95 int num = sysconf(_SC_NPROCESSORS_CONF); 96 printf("system has %d processor(s).\n", num); 97 cpu_set_t cpu_mask; 98 cpu_set_t cur_cpu_info; 99 100 memset((void*)&bits, 0, sizeof(bits_t)); 101 set_cpu(0); 102 which_cpu("main thread"); 103 104 pthread_t pid1; 105 pthread_create(&pid1, NULL, thd_func1, NULL); 106 107 pthread_t pid2; 108 pthread_create(&pid2, NULL, thd_func2, NULL); 109 110 pthread_join(pid1, NULL); 111 pthread_join(pid2, NULL); 112 113 return 0; 114 }
线程id | CPU绑定 | 有无placeholder | 平均耗时(微妙) |
1 | cpu0 | 无 | 2186931 |
2 | cpu1 | 无 | 2033496 |
情况二测试结果:
线程id | CPU绑定 | 有无placeholder | 平均耗时(微妙) |
1 | cpu0 | 有 | 402144 |
2 | cpu1 | 有 | 392745 |
线程id | CPU绑定 | 有无placeholder | 平均耗时(微妙) |
1 | cpu0 | 无 | 716056 |
2 | cpu0 | 无 | 686804 |
情况四测试结果:
线程id | CPU绑定 | 有无placeholder | 平均耗时(微妙) |
1 | cpu0 | 有 | 761421 |
2 | cpu0 | 有 | 884969 |
微信公众号:
猿人谷
如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】
如果您希望与我交流互动,欢迎关注微信公众号
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。