Memory boost无锁spsc_队列缓存内存访问

Memory boost无锁spsc_队列缓存内存访问,memory,boost,cpu-architecture,lock-free,cpu-cache,Memory,Boost,Cpu Architecture,Lock Free,Cpu Cache,我需要非常关注当前多线程项目中的速度/延迟 缓存访问是我试图更好地理解的事情。我不清楚无锁队列(如boost::lockfree::spsc_队列)如何在缓存级别访问/使用内存 我见过队列,其中需要使用者核心操作的大型对象的指针被推入队列 如果使用者核心从队列中弹出一个元素,我认为这意味着该元素(在本例中是指针)已经加载到使用者核心的二级和一级缓存中。但是要访问元素,它是否不需要通过从L3缓存或通过互连(如果另一个线程位于不同的cpu套接字上)查找和加载元素来访问指针本身?如果是这样的话,是否最

我需要非常关注当前多线程项目中的速度/延迟

缓存访问是我试图更好地理解的事情。我不清楚无锁队列(如boost::lockfree::spsc_队列)如何在缓存级别访问/使用内存

我见过队列,其中需要使用者核心操作的大型对象的指针被推入队列

如果使用者核心从队列中弹出一个元素,我认为这意味着该元素(在本例中是指针)已经加载到使用者核心的二级和一级缓存中。但是要访问元素,它是否不需要通过从L3缓存或通过互连(如果另一个线程位于不同的cpu套接字上)查找和加载元素来访问指针本身?如果是这样的话,是否最好只发送一份消费者可以处理的物品副本

谢谢。

C++主要是为您需要的生态系统付费

任何常规队列都允许您选择存储语义(通过值或引用)

然而,这次您订购了一些特殊的东西:您订购了一个无锁队列。 为了不被锁定,它必须能够像原子操作一样执行所有可观察到的修改操作。这自然会限制可以直接在这些操作中使用的类型

您可能会怀疑是否可能存在超过系统本机寄存器大小的值类型(例如,
int64\t

好问题

输入环形缓冲区 事实上,任何基于节点的容器都只需要为所有修改操作交换指针,这在所有现代体系结构上都是原子化的。 但是,任何涉及以非原子顺序复制多个不同内存区域的事情真的会带来无法解决的问题吗

不,想象一个扁平的POD数据项数组。现在,如果将数组视为一个循环缓冲区,则只需在原子上维护缓冲区前端和末端位置的索引。容器可以在空闲时更新内部“脏前端索引”,同时它可以在外部前端之前进行复制。(副本可以使用宽松的内存顺序)。只有在知道整个副本已完成时,才会更新外部前端索引。此更新需要按acq_rel/cst内存顺序[1]

只要容器能够保护
前面
永远不会完全缠绕并到达
后面
的不变量,这就是一笔不错的交易。我认为这个想法是在破坏者图书馆(LMAX的名声)中普及的。你会从中得到机械共振

  • 读/写时的线性内存访问模式
  • 如果可以使记录大小与(多个)物理缓存线对齐,则效果会更好
  • 所有数据都是本地的,除非POD包含该记录之外的原始引用

Boost的
spsc_队列
实际上是如何做到这一点的?
  • 是的,spqc_队列将原始元素值存储在连续对齐的内存块中:(例如,从
    compile_time_size_ringbuffer
    中,该缓冲区位于
    spsc_队列
    下,静态提供最大容量:)

  • 事实上,正如您所看到的,在读或写端只有“内部”索引。这是可能的,因为只有一个写线程,也只有一个读线程,这意味着在写操作结束时可能只有比预期更多的空间

  • 还有其他一些优化:

    • 支持它的平台的分支预测提示(
      不太可能()
    • 可以一次推/弹出一系列元素。当您需要从一个缓冲区/环形缓冲区虹吸到另一个缓冲区时,这将提高吞吐量,特别是当原始元素大小不等于缓存线(整个倍数)时
    • 尽可能使用std::统一化的拷贝
    • 普通构造函数/析构函数的调用将在实例化时进行优化
    • 在所有主要的标准库实现中,单元化的_拷贝都将优化为memcpy(这意味着,如果您的体系结构支持,将使用SSE指令等)
  • 总而言之,我们看到了一个同类最佳的ringbuffer

    使用什么 Boost为您提供了所有选择。您可以选择使元素类型成为指向消息类型的指针。然而,正如您在问题中已经提出的,这种间接级别降低了引用的位置,并且可能不是最优的

    另一方面,如果复制成本很高,那么在元素类型中存储完整的消息类型可能会很高。至少要尝试使元素类型很好地适合缓存线(在Intel上通常为64字节)

    在实践中,您可能会考虑在值中存储频繁使用的数据,并使用指针引用使用较少的数据(除非遍历指针,否则指针的成本会很低)。

    如果您需要那个“附件”模型,请考虑使用引用的数据的自定义分配器,这样您也可以在那里实现内存访问模式。 让你的剖析器引导你。



    [1]我想对于spsc acq\u rel来说应该是可行的,但我对细节有些生疏。作为一项规则,我强调不要自己编写无锁代码。我建议其他任何人效仿我的例子:)

    谢谢你的详细解释
    typedef typename boost::aligned_storage<max_size * sizeof(T),
                                            boost::alignment_of<T>::value
                                           >::type storage_type;
    
    storage_type storage_;
    
    T * data()
    {
        return static_cast<T*>(storage_.address());
    }
    
    static const int padding_size = BOOST_LOCKFREE_CACHELINE_BYTES - sizeof(size_t);
    atomic<size_t> write_index_;
    char padding1[padding_size]; /* force read_index and write_index to different cache lines */
    atomic<size_t> read_index_;