加载和存储是唯一需要重新排序的指令吗?

2023-11-24

我读过很多关于内存排序的文章,它们都只说 CPU 重新排序加载和存储。

CPU(我对 x86 CPU 特别感兴趣)是否只重新排序加载和存储,而不重新排序它拥有的其余指令?


乱序执行保留了按程序顺序运行的错觉对于单线程/核心。这就像C/C++ as-if 优化规则:只要可见效果相同,内部就可以做任何你想做的事。

Separate threads can only communicate with each other via memory, so the global order of memory operations (loads/stores) is the only externally visible side-effect of execution1.

即使有序的 CPU 也可能使其内存操作变得全局可见且无序。 (例如,即使是带有存储缓冲区的简单 RISC 管道也会进行 StoreLoad 重新排序,如 x86)。按顺序启动加载/存储但允许它们无序完成(以隐藏缓存未命中延迟)的 CPU 也可以重新排序加载,如果它没有专门避免它(或者像现代 x86 一样,积极地执行无序)顺序但假装它没有通过仔细跟踪内存顺序)。


一个简单的例子:两个 ALU 依赖链可以重叠

(有关的:http://blog.stuffedcow.net/2013/05/measuring-rob-capacity/有关查找指令级并行性的窗口有多大的更多信息,例如如果你把这个增加到times 200你只会看到有限的重叠。还相关:我写的这个初级到中级的答案关于像 Haswell 或 Skylake 这样的 OoO CPU 如何发现和利用 ILP。)

也可以看看现代微处理器 90 分钟指南!深入了解超标量和乱序执行 CPU。

为了更深入地分析影响lfence在这里,参见了解 lfence 对具有两个长依赖链的循环的影响,以增加长度

global _start
_start:
    mov  ecx, 10000000
.loop:
    times 25 imul eax,eax   ; expands to imul eax,eax  / imul eax,eax / ...
 ;   lfence
    times 25 imul edx,edx
 ;   lfence
    dec  ecx
    jnz  .loop

    xor  edi,edi
    mov  eax,231
    syscall          ; sys_exit_group(0)

建造(与nasm + ld)到 x86-64 Linux 上的静态可执行文件中,这在每个链的预期 750M 时钟周期内运行(在 Skylake 上)25 * 10Mimul 指令乘以 3 个周期延迟。

评论其中之一imul链不会改变运行所需的时间:仍然是 750M 周期。

这是两个依赖链交错执行乱序的明确证明,否则。 (imul吞吐量为每个时钟 1 个,延迟为 3 个时钟。http://agner.org/optimize/。因此,可以混合第三个依赖链,而不会减慢太多速度)。

实际数字来自taskset -c 3 ocperf.py stat --no-big-num -etask-clock,context-switches,cpu-migrations,page-faults,cycles:u,branches:u,instructions:u,uops_issued.any:u,uops_executed.thread:u,uops_retired.retire_slots:u -r3 ./imul:

  • 与两个 imul 链:750566384 +- 0.1%
  • 仅使用 EAX 链:750704275 +- 0.0%
  • 与一个times 50 imul eax,eax chain: 1501010762 +- 0.0%(几乎是预期的两倍慢)。
  • with lfence防止每个 25 块之间重叠imul: 1688869394 +- 0.0%,比慢两倍还差。uops_issued_any and uops_retired_retire_slots都是 63M,高于 51M,而uops_executed_thread仍然是51M(lfence不使用任何执行端口,但显然有两个lfence每条指令花费 6 个融合域微指令。 Agner Fog 只测得 2。)

(lfence串行化指令执行,但不是内存存储)。如果您没有使用 WC 内存中的 NT 加载(这不会意外发生),则除了停止执行后续指令直到前面的指令“本地完成”之外,它是一个无操作。即直到他们retired来自无序核心。这可能就是为什么它的总时间增加了一倍以上:它必须等待最后一个imul在一个块中经历更多的管道阶段。)

lfence在英特尔上总是这样,但是开启并且仅在启用 Spectre 缓解的情况下进行部分序列化.


脚注1:当两个逻辑线程共享一个物理线程(超线程或其他 SMT)时,还有计时侧通道。例如执行一系列独立的imul如果其他超线程不需要端口 1 进行任何操作,则指令将在最新的 Intel CPU 上以每个时钟 1 的速度运行。因此,您可以通过对一个逻辑核心上的 ALU 绑定循环进行计时来测量端口 0 的压力有多大。

其他微架构侧通道(例如缓存访问)更加可靠。例如,Spectre / Meltdown 最容易利用缓存读取侧通道(而不是 ALU)来利用。

但与架构支持的共享内存读/写相比,所有这些侧通道都非常挑剔且不可靠,因此它们仅与安全相关。它们不是故意在同一程序中用于线程之间的通信。


Skylake 上的 MFENCE 是一个像 LFENCE 一样的 OoO 执行屏障

mfenceSkylake 意外阻止乱序执行imul, like lfence,尽管没有记录表明有这种效果。 (有关更多信息,请参阅移至聊天讨论)。

xchg [rdi], ebx(隐式lock前缀)根本不会阻止 ALU 指令的无序执行。替换时总时间仍为750M Cycleslfence with xchg or a lock上述测试中的 ed 指令。

但与mfence,成本高达 1500M 周期 + 2 的时间mfence指示。为了进行对照实验,我保持指令计数相同,但移动了mfence指令彼此相邻,因此imul链之间可以重新排序,时间下降到750M + 2的时间mfence指示。

Skylake 的这种行为很可能是微代码更新修复的结果勘误表 SKL079, 来自 WC 内存的 MOVNTDQA 可能会通过早期的 MFENCE 指令。勘误表的存在表明,以前可以执行后面的指令mfence已完成,所以他们可能进行了暴力修复添加lfenceuop 到微码mfence.

这是有利于使用的另一个因素xchg对于 seq-cst 存储,甚至lock add将某些堆栈内存作为独立的屏障。Linux 已经做了这两件事,但编译器仍然使用mfence为障碍。看为什么具有顺序一致性的 std::atomic 存储使用 XCHG?

(另请参阅关于 Linux 屏障选择的讨论此 Google 网上论坛帖子,包含 3 个单独的使用建议的链接lock addl $0, -4(%esp/rsp)代替mfence作为一个独立的屏障。

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

加载和存储是唯一需要重新排序的指令吗? 的相关文章

随机推荐

  • 如何将文本添加到 pygame 矩形中

    我已经在 pygame 中绘制了一个矩形 但是我需要能够将 Hello 之类的文本放入该矩形中 我怎样才能做到这一点 如果您也能解释一下 将不胜感激 谢谢 这是我的代码 import pygame import sys from pygam
  • 如何使用java生成像apache的htpasswd这样的哈希值

    我在 htpasswd 中使用 强制对密码进行 MD5 加密 来生成哈希 例如我得到的 123 使用 htpasswd 123 gt apr1 kaTbKaLO ewJXRZAKpjaxK4thy2jOp 使用 MD5 摘要 123 gt
  • 如何检查列表是否包含相同顺序的另一个列表

    C 中有没有简单的方法来检查列表是否包含另一个列表 这是一个例子 我有 var list1 new List
  • 虚拟/纯虚拟解释

    如果一个函数被定义为虚函数到底意味着什么 它与纯虚函数相同吗 From 维基百科的虚函数 在面向对象编程中 在 C 和 Object Pascal 等语言中 虚函数或虚方法是可继承且可重写的函数或方法 便于动态分派 这个概念是面向对象编程
  • JavaFX 的 Spinner 在空文本输入上引发 NullPointerException

    我有一个问题 可编辑的 JavaFX 8Spinner导致未捕获NullPointerException如果清除编辑器文本并提交 然后单击递增或递减按钮 这是j8u60 j8u77 如果运气好的话 递增 递减按钮将卡在按下状态 NPE 会继
  • 如何在 Xcode 4 中打开内存浏览器?

    在 Xcode 3 中调试程序时 我经常在单独的窗口中使用内存浏览器来查看缓冲区更改的内容 同时单步执行代码行 当我现在开始使用 Xcode 4 时 我想知道如何打开内存浏览器 我在用户界面中找不到类似的东西 有人可以提供帮助吗 调试时选择
  • Android 10 无法注册 Sensor.TYPE_STEP_COUNTER

    我目前正在开发一个应用程序来跟踪步骤 为了跟踪步骤 我使用手机的以下传感器 传感器 TYPE STEP COUNTER 这对于我测试过的所有设备都运行良好 最近我有机会在三星 S10 设备上测试该应用程序 但它不再跟踪步骤 而它在三星 S9
  • HTML5 视频:可以在视频上放置常规 html 内容

    只是想知道是否有人知道让常规 html 内容 主要是 img 标签 显示在视频顶部 通过 video 标签 的技巧 正如其他人所暗示的 将 HTML 元素放置在VIDEO使用绝对定位的元素 当您尝试在 iPhone iPod 和可能较旧的
  • 如何在 Visual Studio 中提取默认控件模板?

    我想知道如何使用 Visual Studio 提取 获取副本 给定控件的默认模板 我知道这可以使用 Expression Blend 来完成 右键单击控件 编辑模板 gt 编辑副本 然后将默认控件模板复制到我的 Xaml 中 但这可以用 V
  • POST 和 PUT 的不同模型要求

    假设我有一个控制器CatController包括 GET POST 和 PUT 操作 他们都使用相同的Cat资源可能如下所示 public class CatDto public int Id get set Required public
  • 为什么 hazelcast 的默认分区数为 271?选择分区数的参数是什么?

    我刚刚浏览了 hazelcast 文档 它表明数据跨所有节点进行分区 默认情况下在集群 271 中创建的分区数 哪些参数控制正确分区计数值的选择 为什么默认分区数是 271 271 是质数 给定任何键 Hazelcast 都会对键进行哈希处
  • Jenkins:开始构建时发送电子邮件通知

    在 Jenkins 中 有没有办法在开始构建时发送邮件 我知道有一个构建后电子邮件通知 它会在构建成功或失败后向给定收件人发送邮件 同样 我期待类似预构建通知之类的东西 还有一个条件电子邮件 例如 if username null 发送至
  • 我的智能手机每秒可以执行多少次 FFT? (用于执行语音识别)

    我正在探索语音识别和 DSP 因此我想在我的智能手机上实现一个简单的声音频率分析仪 我有一部 iPhone 和一部运行 Android 的三星 Nexus S 我以前在Matlab中做过基本的DSP 根据我的理解 我需要执行 FFT 来获取
  • 如何使用 UWP MarkupExtension 类?

    Fall Creators 更新 SDK 添加了标记扩展类 非常棒 https learn microsoft com en us uwp api windows ui xaml markup markupextension 所以我创建了一
  • 创建交互式应用程序原型的最佳方法是什么?

    这个问题应该从一般的角度来解释 而不是仅仅针对网络应用程序或桌面应用程序 我一直在寻找一种简单易用的方法来为 Web 应用程序创建交互式原型 我想使用一种技术 允许简单的 UI 创建 特别是在进一步迭代中重新创建和修改 UI 用模型数据填充
  • android:TimePickerDialog 阻止用户选择过去的时间,并可以选择具有新日期的未来时间

    我正在使用这个链接Android TimePickerDialog 设置最大时间 我是安卓新手 借助此代码 我无法选择过去的时间 但我们无法选择未来的时间 当在时间选择器对话框模式中选择 12 时 根据第二天而不是过去的一天自动更改为 am
  • AngularJS:扩展输入指令

    我想知道是否可以扩展 Angular 的输入指令 我想将一些侦听器附加到页面上的所有输入字段 我认为你可以用以下方式装饰现有模块 provide decorate 但我不知道如何使用指令 更准确地说是输入指令 来做到这一点 那么 有人能把我
  • 如何将wav文件转换为浮动幅度

    所以我问了标题中的所有内容 我有一个 wav 文件 由 PyAudio 从输入音频编写 我想将其转换为与声级 振幅 相对应的浮点数据 以进行一些傅里叶变换等 有人有将 WAV 数据转换为 float 的想法吗 我已经找到了两种不错的方法来做
  • Java中的字符串和字符数组

    我是一名刚从C 转向Java的学生 在 Java 中 为 String 和 Char 数组定义单独的数据类型的主要原因是什么 两者有什么区别 由于我只学过C 到目前为止我的印象是它们是同一件事 如果可能的话请澄清 String是不可变的 C
  • 加载和存储是唯一需要重新排序的指令吗?

    我读过很多关于内存排序的文章 它们都只说 CPU 重新排序加载和存储 CPU 我对 x86 CPU 特别感兴趣 是否只重新排序加载和存储 而不重新排序它拥有的其余指令 乱序执行保留了按程序顺序运行的错觉对于单线程 核心 这就像C C as