• cache line提升程序性能


    1.局部性

    内存和cache交换的最小单位是cacheline。比如cacheline 64字节,每一次缓存数据的单位都是以一个 CacheLine 64 字节为单位进行存储的。假如说要查询的数据在 L1 中不存在,那么 CPU 的做法是一次性从 L2 中把要访问的数据及其后面的 64 个字节全部缓存进来。假如下一次再执行的时候要访问的指令在上一次已经在 L1 中存在了,那么就直接访问 L1,就不必再从 L2 来读取了,通过一个代码示例来分析:

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<string.h>
    4. int a[10000][10000];
    5. void bad_cache() {
    6. int i = 0,j =0;
    7. for(i = 0; i < 10000; i++) {
    8. for(j = 0; j< 10000; j++) {
    9. a[j][i] = 1;
    10. }
    11. }
    12. }
    13. void good_cache() {
    14. int i = 0,j =0;
    15. for(i = 0; i < 10000; i++) {
    16. for(j = 0; j< 10000; j++) {
    17. a[i][j] = 1;
    18. }
    19. }
    20. }

    调用good_cache的性能数据:

    1. #simpleperf stat -e cache-references,cache-misses cache
    2. Performance counter statistics:
    3. 1,788,304,622 cache-references # 665.594 M/sec (100%)
    4. 7,106,477 cache-misses # 0.397386% miss rate (100%)
    5. Total test time: 2.686779 seconds.

    可以看到共花费2.68s,cache-misses比例很低,而bad_cache的性能数据:

    1. #simpleperf stat -e cache-references,cache-misses cache
    2. Performance counter statistics:
    3. 1,611,937,854 cache-references # 126.103 M/sec (100%)
    4. 171,774,619 cache-misses # 10.656405% miss rate (100%)
    5. Total test time: 12.782758 seconds.

    由于cache-misses率过高导致时间花费了12.78s时间。

    2.cache伪共享(false sharing)

    处理器架构下,多线程并行写入同一内存位置,由于缓存一致性问题会导致性能问题,这种现象称为cache伪共享。

    2.1 结构体对齐cache line

    内核代码中经常看到某个结构体对齐到cache line size。如果有很多结构体的数组,结构体内存对齐将有助于性能提升。

    示例代码:

    1. #define ____cacheline_aligned __attribute__((__aligned__(64)))
    2. #define LOOP 10000 * 10000
    3. struct data {
    4. int32_t x;
    5. }/*____cacheline_aligned*/;
    6. typedef struct data Data;
    7. Data dArray[2];
    8. void f1() {
    9. int64_t i = 0;
    10. for(i = 0; i < LOOP; i++) {
    11. dArray[0].x = 2;
    12. }
    13. printf("f1 complete\n");
    14. }
    15. void f2() {
    16. int64_t i = 0;
    17. for(i= 0; i < LOOP; i++) {
    18. dArray[1].x = 1;
    19. }
    20. printf("f2 complete\n");
    21. }
    22. int main() {
    23. printf("sizeof(Data):%d\n", sizeof(Data));
    24. std::thread t1(f1);
    25. std::thread t2(f2);
    26. t1.join();
    27. t2.join();
    28. printf("complete\n");
    29. return 0;
    30. }

    如果Data结构体不对齐到cache line,那么dArray[0]和dArray[1]会在同一个cacheline上面,两个线程同时修改结构体成员变量,由于缓存一致性机制,会导致缓存失效。

    上面代码的cache-miss比例 6.2%

    1. Performance counter statistics:
    2. 1,217,787,043 cache-references # 1.374 G/sec (100%)
    3. 75,934,256 cache-misses # 6.235430% miss rate (100%)

    将____cacheline_aligned打开以后,结构体对齐到64字节,这样dArray[0]和dArray[1]将分别占用不同的cache line,写cache失效后两者不会互相影响,cache-miss比例0.017%:

    1. 1,216,454,682 cache-references # 1.413 G/sec (100%)
    2. 205,538 cache-misses # 0.016896% miss rate (100%)

    2.2 结构体成员分布在不同cache line

    数据结构中频繁访问的成员可以单独占用一个cache line或者相关的成员在不同的cache line中错开,以提高访问效率。比如linux内核struct zone数据结构中zone->lock和zone->lru_lock两个频繁访问的锁,可以让他们在不同的cache line中,以提高获取锁的效率。

    1. ZONE_PADDING(_pad1_)
    2. /* free areas of different sizes */
    3. struct free_area free_area[MAX_ORDER];
    4. /* zone flags, see below */
    5. unsigned long flags;
    6. /* Write-intensive fields used from the page allocator */
    7. spinlock_t lock;
    8. ZONE_PADDING(_pad2_)
    9. /* Write-intensive fields used by page reclaim */
    10. /* Fields commonly accessed by the page reclaim scanner */
    11. spinlock_t lru_lock;

    3. 数组对齐到cacheline

    1. atomic_long_t vm_zone_stat[NR_VM_ZONE_STAT_ITEMS] __cacheline_aligned_in_smp;
    2. atomic_long_t vm_numa_stat[NR_VM_NUMA_STAT_ITEMS] __cacheline_aligned_in_smp;
    3. atomic_long_t vm_node_stat[NR_VM_NODE_STAT_ITEMS] __cacheline_aligned_in_smp;

    内核代码将vm_zone_stat等几个内存系统频繁访问的数组对齐到cacheline,确保该数组成员在一个cacheline中 

    参考文章:

    【Cache篇】Cache伪共享_漫游嵌入式的博客-CSDN博客_cache伪共享

    【Cache篇】Cache伪共享_漫游嵌入式的博客-CSDN博客_cache伪共享

  • 相关阅读:
    linux 音视频架构 linux音视频开发
    深度学习入门(8)激活函数ReLU和 Sigmoid的计算图表示方式及代码实现
    考研C语言复习进阶(5)
    基于单片机的智能蓝牙避障循迹小车
    Kmeans
    HBase 常见问题总结(一)
    Flask-APScheduler 常见摸坑指南
    使用Portman、Postman和Newman自动化API测试
    Dev C++开发环境的配置及使用
    【EI会议征稿】第三届信号处理与通信技术国际学术会议(SPCT 2023)
  • 原文地址:https://blog.csdn.net/GetNextWindow/article/details/126557860