Advertisement

什么是CPU cache line和cache line bouncing?

阅读量:

什么是Cache Line?

由处理器进行的高速缓存空间分配负责管理CPU与主存储器之间的数据传输关系。当处理器访问尚未位于高速缓存中的内存一部分时,其会加载被访问地址周边的一块内存至高速缓存中,并期望其很快即被再次使用。由高速缓存处理的数据单位被称为memory blocks(记忆体区块),这些块的尺寸则被称为cache line size(Cache Line Size)。通常情况下,默认使用的快取行尺寸包括32位、64位及128位三种标准配置。一个拥有64字节长的 cache lines 的 64KB 级快速存储单元总计包含 1024 个 cache lines(Cache Lines)。

借助于gcc编译器中的特定编译指令__attribute__,我们可以实现结构体在内存中的布局优化。

复制代码
    #include <iostream>
    using namespace std;
    
    #define CACHELINE_SIZE 64
    #define CACHELINE_ALIGNMENT __attribute__((aligned(CACHELINE_SIZE)))
    
    // http://gcc.gnu.org/onlinedocs/gcc-4.5.2/gcc/Type-Attributes.html
    
    
    void PrintStruct1() {
    struct MyStruct {
        int a;      // 4
        char b;     // 1
        double c;   // 8
    };              // 结构体对齐,变成4+1+3+8 = 16
    // sizeof()打印结构体的总大小,offset()打印每个成员相对于结构体开头的偏移量
    printf("Sizeof MyStruct1: %zu bytes\n", sizeof(MyStruct));       // 16
    
    printf("Offset of 'a': %zu bytes\n", offsetof(MyStruct, a));    // 0
    printf("Offset of 'b': %zu bytes\n", offsetof(MyStruct, b));    // 4
    printf("Offset of 'c': %zu bytes\n", offsetof(MyStruct, c));    // 8
    }
    
    // __packed__: This attribute, attached to struct or union type definition, 
    // specifies that each member (other than zero-width bitfields) of the 
    // structure or union is placed to minimize the memory required.
    void PrintStruct2() {
    struct MyStruct {
        int a;
        char b;
        double c;
    } __attribute__ ((__packed__));
    printf("Sizeof MyStruct2: %zu bytes\n", sizeof(MyStruct));       // 13
    
    printf("Offset of 'a': %zu bytes\n", offsetof(MyStruct, a));    // 0
    printf("Offset of 'b': %zu bytes\n", offsetof(MyStruct, b));    // 4
    printf("Offset of 'c': %zu bytes\n", offsetof(MyStruct, c));    // 5
    }
    
    // __aligned__: This attribute specifies a minimum alignment (in bytes) for variables of the specified type.
    void PrintStruct3() {
    struct MyStruct {
        int a;
        char b;
        double c;
    } __attribute__ ((__aligned__(64)));     // 指定64字节对齐
    printf("Sizeof MyStruct3: %zu bytes\n", sizeof(MyStruct));       // 64
    
    printf("Offset of 'a': %zu bytes\n", offsetof(MyStruct, a));    // 0
    printf("Offset of 'b': %zu bytes\n", offsetof(MyStruct, b));    // 4
    printf("Offset of 'c': %zu bytes\n", offsetof(MyStruct, c));    // 8
    }
    
    void PrintStruct4() {
    struct MyStruct {
        int a CACHELINE_ALIGNMENT;    // 指定64字节对齐
        char b;
        double c;
    };
    printf("Sizeof MyStruct4: %zu bytes\n", sizeof(MyStruct));      // 64
    
    printf("Offset of 'a': %zu bytes\n", offsetof(MyStruct, a));    // 0
    printf("Offset of 'b': %zu bytes\n", offsetof(MyStruct, b));    // 4
    printf("Offset of 'c': %zu bytes\n", offsetof(MyStruct, c));    // 8
    }
    
    void PrintStruct5() {
    struct MyStruct {
        int a;
        char b CACHELINE_ALIGNMENT;
        double c;
    };
    printf("Sizeof MyStruct5: %zu bytes\n", sizeof(MyStruct));      // 128
    
    printf("Offset of 'a': %zu bytes\n", offsetof(MyStruct, a));    // 0
    printf("Offset of 'b': %zu bytes\n", offsetof(MyStruct, b));    // 64
    printf("Offset of 'c': %zu bytes\n", offsetof(MyStruct, c));    // 72
    }
    
    void PrintStruct6() {
    struct MyStruct {
        int a;
        char b;
        double c CACHELINE_ALIGNMENT;
    };
    printf("Sizeof MyStruct6: %zu bytes\n", sizeof(MyStruct));      // 128
    
    printf("Offset of 'a': %zu bytes\n", offsetof(MyStruct, a));    // 0
    printf("Offset of 'b': %zu bytes\n", offsetof(MyStruct, b));    // 4
    printf("Offset of 'c': %zu bytes\n", offsetof(MyStruct, c));    // 64
    }
    int main() {
    PrintStruct1();
    PrintStruct2();
    PrintStruct3();
    PrintStruct4();
    PrintStruct5();
    PrintStruct6();
    return 0;
    }
    
    
    
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    
    代码解读

什么是cache line bouncing?

多个共享内存处理器按照缓存线粒度运行,在传输数据时会传递一个缓存线(始终保持对齐),因此缓存线本质上是一个一直在跳跃(bouncing)的球。例如:

假设处理器1试图获取自旋锁(spinlock)。通常占据8字节内存空间,并对其对齐。首先,在这种情况下:若处理器1的缓存中未找到该self-protecting lock identifier,则会将其对应的缓存行加载到内存中。若该self-protecting lock identifier当前未被占用,则会立即获取该资源。完成后立即释放该资源。接着,在这种情况下: 处理器2也会尝试获取同一个self-protecting lock identifier。从缓存中取出相关缓存行,并将其加载到自己的缓存区。接着,在这种情况下: 处理器2也会立即获取该资源。完成后立即释放该资源。

再次使用上述两个步骤,在具有两个处理器之间的缓存线中实现重叠。最糟糕的情况是:如果缓存线由64字节组成,并且每个自旋锁仅有8字节,在同一缓存线包含多个自旋锁的情况下,“false sharing”的现象会更加明显。具体而言,在同一个缓存线中存在自旋锁A和自旋锁B时,则会导致以下情况:处理器1仅需获取自旋锁A、而处理器2则仅需获取自旋锁B。因此,在跨处理器操作过程中,“false sharing”现象不可避免地会发生。这种情况被称为false sharing——虽然这两个处理器并未真正共享任何数据内容,在数据交错的过程中会产生显著的影响。

因此,当一个变量经常需要跨线程读写时,应该将其按照cache line对齐。

Cache Line Bouncing Test: https://arighi.blogspot.com/2008/12/cacheline-bouncing.html

全部评论 (0)

还没有任何评论哟~