在rigtorp的SPSCQueue中使用索引缓存的目的是什么

问题描述 投票:0回答:2

我正在阅读rigtorp的SPSCQueue的实现,这是一个非常优雅的设计并且具有非常好的基准。

我了解自述文件中描述的大部分设计哲学。我不明白的是

writeIdxCache_
readIdxCache_
有什么帮助。

查看以下用于从队列中获取项目的函数

案例 0:原始实现

  RIGTORP_NODISCARD T *front() noexcept {
    auto const readIdx = readIdx_.load(std::memory_order_relaxed);
    if (readIdx == writeIdxCache_) {
      writeIdxCache_ = writeIdx_.load(std::memory_order_acquire);
      if (writeIdxCache_ == readIdx) {
        return nullptr;
      }
    }
    return &slots_[readIdx + kPadding];
  }

如果我将上面的实现与下面的实现进行比较,有什么好处?

情况1:不使用缓存

首先使用

writeIdxCache_
有什么好处

  RIGTORP_NODISCARD T *front() noexcept {
    auto const readIdx = readIdx_.load(std::memory_order_relaxed);
    if (readIdx == writeIdx_.load(std::memory_order_acquire)) {
        return nullptr;
    }
    return &slots_[readIdx + kPadding];
  }

情况2:直接设置
readIdxCache_

这里我在加载的时候也直接设置了

readIdxCache_
。这种方法是否仍然有效,或者会破坏队列?它比案例0更好还是更差?

  RIGTORP_NODISCARD T *front() noexcept {
    readIdxCache_ = readIdx_.load(std::memory_order_relaxed);
    if (readIdxCache_ == writeIdxCache_) {
      writeIdxCache_ = writeIdx_.load(std::memory_order_acquire);
      if (writeIdxCache_ == readIdxCache_) {
        return nullptr;
      }
    }
    return &slots_[readIdx + kPadding];
  }
queue cpu-architecture cpu-cache micro-optimization lock-free
2个回答
2
投票

对于将存储提交到缓存行的 CPU,它需要该缓存行的独占所有权。 (MESI已修改或独占状态)。 如果一个线程正在写入共享变量并且没有其他线程正在读取它,那么事情就会很高效,并且包含它的缓存行可以保持该状态。

但是,如果另一个线程读取它,其 MESI 共享请求会将缓存行从“修改”转换为“共享”,因此写入器在提交下一个存储之前需要读取所有权。 此外,如果写入器自上次读取后已写入,则执行读取的线程将必须等待其加载的缓存未命中(共享请求)。 (读取所有权会使其他核心中缓存行的所有其他副本无效。)

如果没有索引缓存,每次调用

front()
都会读取两个共享变量,包括另一个线程有时写入的变量。 这会在
writeIdx_
上产生争用,因为它必须针对大多数读取和大多数写入发送缓存一致性消息。 (对于作者来说,每次阅读都会在
readIdx_
上引发争议。)

使用索引缓存,

front()
仅偶尔接触
writeIdx_
,只有在读取了上次看到的所有队列条目之后。 同样,写入者也会避免经常接触
readIdx_
,因此,如果您要从队列中删除条目,则写入 
readIdx_
 会非常高效。


0
投票
重新。

case 2: directly set readIdxCache_


这个坏了。

readIdxCache_

 纯粹是编写器线程本地的。它不能/不应该与读者分享。

readIdxCache_

 是一种优化(正如 Peter 指出的那样),用于减少编写者访问读者光标(特别是缓存行)的频率。

即一旦设置了 readIdxCache_,写入器现在可以取得进展,直到其光标到达 readIdxCache_ -

在它需要再次命中当前读取器光标(缓存行)以刷新 readIdxCache_ 之前。

除了将读取器的光标带到写入器的一级缓存之外,
这使得读者光标所在的下一个商店更加昂贵
因为它现在必须发出 RFO 才能将共享缓存线带回到独占状态 (MESI)。

© www.soinside.com 2019 - 2024. All rights reserved.