除了提供必要的保证之外,硬件内存屏障是否还能使原子操作的可见性更快?

2024-03-04

TL;DR:在生产者-消费者队列中,放置不必要的(从 C++ 内存模型的角度来看)内存栅栏或不必要的强内存顺序是否有意义,以牺牲可能更差的吞吐量为代价获得更好的延迟?


C++ 内存模型在硬件上执行,通过为更强的内存顺序设置某种内存栅栏,而不是在较弱的内存顺序上设置它们。

特别是,如果生产者这样做store(memory_order_release),消费者观察存储的价值load(memory_order_acquire),装载和存储之间没有栅栏。在 x86 上根本没有栅栏,在 ARM 上栅栏在存储之前和加载之后进行操作。

没有围栏存储的值最终将被没有围栏的负载观察到(可能在几次不成功的尝试之后)

我想知道在队列的两侧放置栅栏是否可以使值被更快地观察到? 如果有的话,有和没有围栏的延迟是多少?

我希望只是有一个循环load(memory_order_acquire) and pause / yield限制为数千次迭代是最好的选择,因为它随处可见,但想了解原因。

由于这个问题与硬件行为有关,因此我希望没有通用答案。如果是这样,我主要想知道 x86(x64 风格),其次是 ARM。


Example:

T queue[MAX_SIZE]

std::atomic<std::size_t>   shared_producer_index;

void producer()
{
   std::size_t private_producer_index = 0;

   for(;;)
   {
       private_producer_index++;  // Handling rollover and queue full omitted

       /* fill data */;

      shared_producer_index.store(
          private_producer_index, std::memory_order_release);
      // Maybe barrier here or stronger order above?
   }
}


void consumer()
{
   std::size_t private_consumer_index = 0;

   for(;;)
   {
       std::size_t observed_producer_index = shared_producer_index.load(
          std::memory_order_acquire);

       while (private_consumer_index == observed_producer_index)
       {
           // Maybe barrier here or stronger order below?
          _mm_pause();
          observed_producer_index= shared_producer_index.load(
             std::memory_order_acquire);
          // Switching from busy wait to kernel wait after some iterations omitted
       }

       /* consume as much data as index difference specifies */;

       private_consumer_index = observed_producer_index;
   }
}

对核间延迟基本没有明显影响,如果您怀疑缓存中丢失的后续加载可能存在任何争用,那么在没有仔细分析的情况下绝对不值得“盲目”使用。

一个常见的误解是需要使用 asm 屏障来使存储缓冲区提交到缓存。事实上,障碍只会让这个核心等待一些已经会自行发生的事情,然后再进行后续加载和/或存储。对于完整的屏障,阻止以后的加载和存储,直到存储缓冲区耗尽。英特尔硬件上存储缓冲区的大小?存储缓冲区到底是什么? https://stackoverflow.com/questions/54876208/size-of-store-buffers-on-intel-hardware-what-exactly-is-a-store-buffer

在过去糟糕的日子里std::atomic, 编译器障碍是阻止编译器将值保留在中的一种方法寄存器(CPU核心/线程私有,不连贯),但这是编译问题而不是asm。具有非一致性缓存的 CPU 理论上是可能的(其中 std::atomic 需要进行显式刷新以使存储可见),但是实际上,没有实现在具有非一致性缓存的核心之间运行 std::thread https://stackoverflow.com/questions/4557979/when-to-use-volatile-with-multi-threading/58535118#58535118.


如果我不使用栅栏,一个核心需要多长时间才能看到另一个核心的写入? https://stackoverflow.com/questions/51292687/if-i-dont-use-fences-how-long-could-it-take-a-core-to-see-another-cores-write高度相关,我之前至少已经写过几次这个答案。 (但这看起来是一个专门回答这个问题的好地方,而不必纠结于哪些障碍的作用。)


可能会有一些非常小的副作用阻止可能与 RFO 竞争的后续加载(让该核心获得对缓存行的独占访问权以提交存储)。 CPU 总是尝试尽快耗尽存储缓冲区(通过提交到 L1d 缓存)。一旦存储提交到 L1d 缓存,它就对所有其他核心全局可见。 (因为他们是一致的;他们仍然需要提出共享请求......)

如果另一个核心上的负载发生在该存储提交之后,则让当前核心将一些存储数据写回到 L3 缓存(尤其是在共享状态下)可以减少未命中损失。但没有好的方法可以做到这一点。制造冲突 https://stackoverflow.com/questions/54209039/x86-mesi-invalidate-cache-line-latency-issue如果生产者性能除了为下一次读取创建低延迟之外并不重要,那么 L1d 和 L2 中可能会丢失。

On x86, 英特尔特雷蒙 https://fuse.wikichip.org/news/1158/intel-discloses-tremont-a-goldmont-plus-successor/(低功耗Silvermont系列)将介绍cldemote https://github.com/HJLebbink/asm-dude/wiki/CLDEMOTE ()将一行写回到外部缓存,但不会一直写到 DRAM。 (clwb可能会有所帮助,但确实会迫使存储一直使用 DRAM。此外,Skylake 实现只是一个占位符,其工作方式类似于clflushopt.)

  • 有没有办法编写Intel CPU直接核对核通信代码? https://stackoverflow.com/questions/58741806/is-there-any-way-to-write-for-intel-cpu-direct-core-to-core-communication-code
  • 如何强制CPU核心刷新C中的存储缓冲区? https://stackoverflow.com/questions/54067605/how-to-force-cpu-core-to-flush-store-buffer-in-c/54068535#54068535
  • x86 MESI 无效缓存线延迟问题 https://stackoverflow.com/questions/54209039/x86-mesi-invalidate-cache-line-latency-issue
  • 强制将缓存行迁移到另一个核心 https://stackoverflow.com/questions/57336172/force-a-migration-of-a-cache-line-to-another-core(不可能)

有趣的事实:PowerPC 上的非 seq_cst 存储/加载可以在同一物理核心上的逻辑核心之间进行存储转发,从而使存储对some在其他核心变得全局可见之前all其他核心。据我所知,这是线程不同意所有对象的全局存储顺序的唯一真正的硬件机制。其他线程是否总是以相同的顺序看到对不同线程中不同位置的两个原子写入? https://stackoverflow.com/questions/27807118/will-two-atomic-writes-to-different-locations-in-different-threads-always-be-see/50679223#50679223。在其他 ISA 上,包括 ARMv8 和 x86,可以保证存储同时对所有其他内核可见(通过提交到 L1d 缓存)。


对于负载,CPU 已经将需求负载优先于任何其他内存访问(因为当然执行必须等待它们。)加载之前的屏障只能延迟它。

如果这使它看到它正在等待的存储,而不是“太快”并看到旧的缓存的无聊值,那么这可能恰好是最佳的时机巧合。但通常没有理由假设或预测pause或在负载之前设置屏障可能是一个好主意。

负载后的障碍物也无济于事。稍后的加载或存储可能能够启动,但无序的 CPU 通常会以最早的优先级执行操作,因此在此加载有机会获得其加载请求之前,后面的加载可能无法填满所有未完成的加载缓冲区发送到核心外(假设由于最近存储了另一个核心而导致缓存未命中。)

我想我可以想象如果这个加载地址暂时没有准备好(指针追逐情况)并且当地址已知时最大数量的非核心请求已经在进行中,那么对以后的障碍会有好处。

几乎可以肯定,任何可能的好处都是不值得的;如果有很多独立于该负载的有用工作可以填满所有非核心请求缓冲区(Intel 上的 LFB),那么它很可能不在关键路径上,并且让这些负载运行可能是一件好事。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

除了提供必要的保证之外,硬件内存屏障是否还能使原子操作的可见性更快? 的相关文章

  • 为什么在排序输入上插入到树中比随机输入更快?

    现在我一直听说从随机选择的数据构建二叉搜索树比有序数据更快 这仅仅是因为有序数据需要显式重新平衡以将树高度保持在最低限度 最近我实现了一个不可变的treap http en wikipedia org wiki Treap 一种特殊的二叉搜
  • 为什么模板类的静态成员不唯一

    看一下下面的代码 include
  • 如何在 Asp.Net Core 6 中向类型化 HttpClient 添加承载令牌身份验证

    我正在尝试使用 ASP Net Core 6 设置一个 Web api 以便用户可以到达我的端点 然后我使用特权帐户在幕后的 D365 中执行一些工作 我正在使用类型化的 HTTP 客户端 但我不确定如何插入承载身份验证 以便来自该客户端的
  • 为什么迭代器类型推导失败? [复制]

    这个问题在这里已经有答案了 为什么这在 C 中不起作用 为什么我不能限制foo的参数为std vector
  • 基于多线程的 RabbitMQ 消费者

    我们有一个 Windows 服务 它监听单个 RabbitMQ 队列并处理消息 我们希望扩展相同的 Windows 服务 以便它可以监听 RabbitMQ 的多个队列并处理消息 不确定使用多线程是否可以实现这一点 因为每个线程都必须侦听 阻
  • C# 中输入按键

    我尝试了这段代码 private void textBox1 KeyPress object sender KeyPressEventArgs e if Convert ToInt32 e KeyChar 13 MessageBox Sho
  • 这种对有效类型规则的使用是否严格遵守?

    C99和C11中的有效类型规则规定 没有声明类型的存储可以用任何类型写入 并且存储非字符类型的值将相应地设置存储的有效类型 抛开 INT MAX 可能小于 123456789 的事实不谈 以下代码对有效类型规则的使用是否严格符合 inclu
  • 仅针对某些异常类型中断

    我知道异常处理是一件非常重要的事情 我们在所有项目中都在这样做 主要原因是记录客户发生的错误 这工作正常 根本不是问题 但是 当我仍在使用 Visual Studio 编码和运行应用程序时 我根本不需要任何异常处理 我希望调试器正好停在应用
  • 使用正则表达式解析日志文件

    我目前正在为我们的内部日志文件 由 log4php log4net 和 log4j 生成 开发一个解析器 到目前为止 我有一个很好的正则表达式来解析日志 除了一个烦人的一点 一些日志消息跨越多行 我无法正确匹配 我现在的正则表达式是这样的
  • 如何在 C# 中创建 PKCS12 .p12 文件?

    这可能是一个n00b问题 但我在这方面确实没有任何经验 我需要创建一个包含 X509 证书和私钥的 p12 捆绑包 我当前有两个对象 X509Certificate2 和包含关键信息的 RSAParameters 对象 如何将它们合并到 p
  • 代码块 - 使用大地址感知标志进行编译

    如何使用以下命令在 64 位系统上编译 32 位应用程序LARGE ADRESS AWARE使用代码块标记 我需要使用超过 2GB 的内存 应该是添加的情况 Wl large address aware到链接标志 我不使用 CodeBloc
  • 是否可以在对Where 的调用中调用命名方法?

    我试图从 RedGate 的这本免费电子书中了解 Linq 的一些性能影响ftp support red gate com ebooks under the hood of net memory management part1 pdf f
  • PowerShell 与 MongoDB C# 驱动程序方法不兼容?

    由 C 泛型引起的最新 MongoDB 驱动程序的问题 Cannot find an overload for GetCollection and the argument count 1 我可能可以使用其他没有泛型的 GetCollect
  • C# 从今天起 30 天

    我需要我的应用程序从今天起 30 天后过期 我会将当前日期存储在应用程序配置中 如何检查应用程序是否已过期 我不介意用户是否将时钟调回来并且应用程序可以正常工作 用户太愚蠢而不会这样做 if appmode Trial string dat
  • 从 cin 读取整数序列并将它们存储在向量中

    这就是我读取整数的方法std cin并将它们存储在向量中 int number vector
  • 如何在RcppParallel中调用用户定义的函数?

    受到文章的启发http gallery rcpp org articles parallel distance matrix http gallery rcpp org articles parallel distance matrix 我
  • 没有 FPU 的处理器中的浮点计算

    是否可以在没有浮点单元的嵌入式处理器中执行浮点运算 是的 您只需要在软件中完成即可 你的编译器可能会提供支持 http gcc gnu org onlinedocs gccint Soft float library routines ht
  • 如何使用 MongoDB 实现 ASP.NET Core 3.1 Identity?

    是一个 API 用于简化后端和逻辑代码来管理用户 密码 个人资料数据 角色 声明 令牌 电子邮件确认等 对于 Visual Studio 来说 支撑脚手架 https learn microsoft com en us aspnet cor
  • 曲线/路径骨架二值图像处理

    我正在尝试开发一个可以处理图像骨架的路径 曲线的代码 我想要一个来自两点之间骨架的点向量 该代码在添加一些点后结束 我没有找到解决方案 include opencv2 highgui highgui hpp include opencv2
  • OpenCV 仅围绕大轮廓绘制矩形?

    第一次发帖 希望我以正确的方式放置代码 我正在尝试检测和计算视频中的车辆 因此 如果您查看下面的代码 我会在阈值处理和膨胀后找到图像的轮廓 然后我使用 drawContours 和矩形在检测到的轮廓周围绘制一个框 我试图在 drawCont

随机推荐