用C++编写的Dmitry Vyukov优秀的有界MPMC队列

他添加了一些填充变量.我认为这是为了使其与缓存线对齐以提高性能.

我有几个问题要问.

  1. 为什么要这样做?
  2. 这是一种便携的方法吗 一如既往地工作
  3. 在什么情况下使用__attribute__ ((aligned (64)))最好呢?
  4. 为什么在缓冲区指针之前填充会有助于提高性能?难道不只是指针加载到缓存中,所以它实际上只有指针的大小吗?

    static size_t const     cacheline_size = 64;
    typedef char            cacheline_pad_t [cacheline_size];
    
    cacheline_pad_t         pad0_;
    cell_t* const           buffer_;
    size_t const            buffer_mask_;
    cacheline_pad_t         pad1_;
    std::atomic<size_t>     enqueue_pos_;
    cacheline_pad_t         pad2_;
    std::atomic<size_t>     dequeue_pos_;
    cacheline_pad_t         pad3_;
    

在gcc for c代码下,这个概念会起作用吗?

推荐答案

这样一来,修改不同字段的不同内核就不必在缓存之间跳转包含这两个字段的缓存线.通常,处理器要访问内存中的某些数据,包含该数据的整个缓存线必须位于该处理器的本地缓存中.如果正在修改该数据,则该缓存条目通常必须是系统中任何缓存中的唯一副本(MESI/MOESI style cache coherence protocols中的独占模式).当不同的内核试图修改恰好位于同一缓存线上的不同数据,从而浪费时间来回移动整条缓存线时,这就是所谓的false sharing.

在您给出的特定示例中,一个内核可以将一个条目(读取(共享)buffer_和写入(独占)enqueue_pos_)排入队列,而另一个内核(共享buffer_和独占dequeue_pos_)则不会在另一个内核拥有的缓存线上暂停.

开头的填充意味着buffer_buffer_mask_结束在同一高速缓存线上,而不是分成两条线,因此需要两倍的存储器通信量才能访问.

我不确定这项技术是否完全可移植假设每个cacheline_pad_t本身将与64字节(其大小)的缓存线边界对齐,因此它后面的内容将位于下一个缓存线上.据我所知,C语言和C++语言标准只要求整个 struct ,这样它们可以很好地在数组中生存,而不违反任何成员的对齐要求.罢工>(见 comments )

attribute方法将更特定于编译器,但可能会将此 struct 的大小减半,因为填充将限于将每个元素四舍五入到完整的缓存线.如果一个人有很多这样的东西,那将是非常有益的.

同样的概念适用于C以及C++.

C++相关问答推荐

当我try 计算一个多项时,看到segfault.我做错了什么?

海湾合作委员会是否保证大小匹配的访问?

是否可以在C中进行D3 D12申请?

设计处理各种数据类型的方法和数据 struct

C中的__attributor__((aligned(4),packed))与 struct 的用法

在C中使用动态内存分配找到最小的负数

ARM64 ASIMD固有的加载uint8_t* 到uint16x8(x3)?

试图从CSV文件中获取双精度值,但在C++中始终为空

是否可以使用指针算法在不对齐的情况下在 struct 中相同类型的字段的连续序列之间移动?

在循环中复制与删除相同条件代码的性能

获取每个循环迭代结束时的当前时间

Square不与Raylib一起移动

ifdef __cplusplus中的整数文字单引号

如何在GDB中查看MUSL的源代码

unions 的原子成员是个好主意吗?

赋值两侧的后置增量,字符指针

我编写这段代码是为了判断一个数字是质数、阿姆斯特朗还是完全数,但由于某种原因,当我使用大数时,它不会打印出来

free后内存泄漏?

C 中从 Unix 纪元时间转换的损坏

在 C 中的 scanf() 格式说明符中使用宏获取字符串长度