在最新的 64 位 Intel CPU 上,如何在没有隐式锁的情况下与寄存器交换堆栈顶部?

2023-12-06

x64 调用约定使用寄存器最多前 4 个参数(rcx, rdx, r8, r9)并将其余参数传递到堆栈上。 在这种情况下,处理补充参数的明显方法是asm程序如下:

procedure example(
  param1, //rcx
  param2, //rdx
  param3, //r8
  param4, //r9
  param5,
  param6
);
asm
  xchg param5, r14 // non-volatile registers, should be preserved
  xchg param6, r15 // non-volatile registers, should be preserved

  // ... procedure body, use r14–r15 for param5–param6

  mov r15, param6
  mov r14, param5  
end;

但这里有一个很大的问题:如果涉及到内存操作,XCHGIntel CPU 中的指令有一个隐式的LOCK这也意味着巨大的性能损失;也就是说,在最坏的情况下,总线将被锁定数百个时钟周期。 (顺便说一句,我无法真正理解这种隐含的LOCK因为拥有真正可用且智能的互锁指令,例如XADD, CMPXCHG, BTS/BTRETC。;裸体的XCHG如果我需要线程同步,这将是我的最后一个选择。)那么,如果我想要一些简短而优雅的内容来在寄存器中使用/保存/恢复 params5 和 params6 ,我应该做什么?是否有可能防止总线锁定的黑客XCHG指示?一般来说,针对这种情况,标准的、广泛使用的方法是什么?


正如罗斯的回答所解释的那样,广泛使用的标准方法是溢出(然后重新加载)其他内容以释放 tmp 寄存器。

首先将所有内容加载到寄存器中,而不是根据需要加载,这就是搬起石头砸自己的脚。有时您甚至可以使用 arg 作为内存源操作数,而无需单独的mov根本没有负载。


但回答一下标题的问题:

尽管有问题标题,我的回答是8086汇编语言中交换2个寄存器(16位)确实有效地解决了寄存器与内存的交换,避免了xchg因为隐含的lock字首。溢出(然后重新加载)一个 tmp reg,或者在最坏的情况下,在 reg 和 mem 之间进行异或交换。那是horrible,基本上可以说明为什么您的整个方法会导致实施效率低下。

(正如 Ross 所说,你可能(还)无法编写比编译器更高效的 asm。一旦你了解了如何创建高效的 asm(Agner Fog 的优化指南和微架构指南:https://agner.org/optimize/,以及其他链接https://stackoverflow.com/tags/x86/info)并且可以发现优化编译器输出中的实际低效率,然后您could如果您愿意,有时可以手写更好的汇编。 (通常以编译器输出作为起点)。但通常情况下,如果可能的话,您只需利用这些经验来调整您的 C 源代码,以便从编译器中获得更好的汇编,因为从长远来看,这更有用/可移植。而且它很少重要到值得手写 asm。

此时,您更有可能通过查看来学习提高汇编效率的技术gcc -O3输出。但错过的优化并不罕见,如果您发现一些优化,您可以在 GCC 的 bugzilla 上报告它们。)


隐含的——lock的语义xchg来自386。 The lock前缀自 8086 起就存在,用于与类似指令一起使用add/or/and/etc [mem], reg or immediate。包括lock xchg,这显然没有隐含的lock行为(即使没有前缀)直到 386。或者可能在那之前没有记录?我不知道英特尔为何做出这样的改变。也许对于原始的 SMP 386 系统来说。

您提到的其他说明是后来添加的: bts/btr/btc在 386 中(但不仅仅用于共享内存,因此隐式lock没有意义)。

xadd486年,以及cmpxchg直到奔腾。 (486 有一个未记录的操作码cmpxchg, see NASM 附录 A 的旧版本以获得对此的评论)。这些 CPU 的设计晚于 386,大概是在对原始 SMP 系统有一些初步经验之后。

正如你所说,英特尔明智地选择了not make lock尽管主要用例是多线程代码中的原子操作,但这些新指令是隐式的。 SMP x86 机器开始成为 486 和 Pentium 的一部分,但 UP 机器上的线程之间不需要同步lock。这是一个相反的问题x86 CMPXCHG 是原子的吗?如果是的话,为什么它需要 LOCK?

8086 是一台单处理器机器,因此对于软件线程之间的同步,简单add [mem], reg对于中断来说已经是原子的,因此对于上下文切换来说已经是原子的。 (并且不可能同时执行多个线程)。遗产#LOCK文档仍然提到的外部信号只重要。 DMA 观察器,或用于 MMIO 到设备上的 I/O 寄存器(而不是普通 DRAM)。

(在现代 CPU 上,xchg [mem], reg在未跨缓存行边界分割的可缓存内存上,仅采用缓存锁,确保该行从加载读取 L1d 到存储提交到 L1d 保持在 MESI Exclusive 或 Modified 状态。)

我不知道为什么 8086 架构师(主要是 Stephen Morse 设计了指令集)选择不制作非原子xchg有可用内存。 (更正,我认为他做到了,而且只是 386 改变了它;这个答案最初是在我知道这是 386 改变之前写的。)也许在 8086 上,让 CPU 断言并没有慢多少#LOCK在进行存储+加载事务时?但随后我们就陷入了 x86 其余部分的这些语义。 x86 设计很少具有前瞻性,如果主要用例xchg用于原子 I/O 然后它节省了代码大小lock隐含的。


没有办法禁用隐式锁xchg [mem], reg

您需要使用多个不同的指令。异或交换是可能的,但效率很低。也许还没有那么糟糕xchg,取决于微架构和周围的代码(在执行任何后续加载之前等待所有先前的存储执行并提交到 L1d 缓存是多么糟糕)。例如与内存目标相比,一些运行中的缓存未命中存储可能会变得非常昂贵xor它可以将数据留在存储缓冲区中。

编译器基本上不会使用xchg即使在寄存器之间(因为不比3便宜mov英特尔的说明,所以它通常不是一个有用的窥视孔优化来寻找)。他们仅用它来实现std::atomic商店有seq_cst内存顺序(因为它比mov + mfence在大多数 uarch 上:为什么具有顺序一致性的 std::atomic 存储使用 XCHG?),并实施std::atomic::exchange. But not std::swap与 reg 或内存。

如果 x86 有非原子 2 或 3 uop,它有时会很有用swap reg,mem,但事实并非如此。没有这样的指令。

但特别是对于具有 16 个寄存器的 x86-64,您只会遇到这个问题,因为您自己创建了它。给自己留一些临时的计算规则。

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

在最新的 64 位 Intel CPU 上,如何在没有隐式锁的情况下与寄存器交换堆栈顶部? 的相关文章

  • 64 位 Windows 汇编器

    我想对 64 位 Windows 程序集进行编程 最好使用 NASM 我在 google 上查了一下 但似乎找不到 64 位 Windows 编译器 有些网站提到了ml64 但它似乎不再包含在VC 中 我尝试过 32 位程序集 但显然它在我
  • 如何在 MacOS 上使用 nasm 进行编译

    我正在尝试在汇编器上编译并链接我的第一个程序 我尝试编译以下代码 include stud io inc global main section text main xor eax eax again PRINT Hello PUTCHAR
  • 如何计算汇编中的内存位移?

    我一直在研究 yasm 汇编语言 并生成了一个包含以下内容的列表文件 我需要帮助理解第一列中的内存位移是如何计算的 提前致谢 1 line 1 1 memory asm 2 section data 3 00000000 04000000
  • 嵌入式系统:使用汇编语言时的内存布局

    根据我的理解 嵌入式系统运行机器代码 有多种方法可以生成此代码 一种是用 C 等高级语言编写程序 然后使用编译器获得这样的代码 另一种方法是用汇编语言为该嵌入式系统编写指令 并使用汇编器将其转换为机器代码 现在我们得到了加载到系统并执行的机
  • 可以从操作系统内禁用/配置 Intel PT(处理器跟踪)吗?

    我有很多关于 Intel PT 的问题 一直在尝试解码手册 但非常困难 我的问题是 我试图找出 Intel PT 是否可以从操作系统内部禁用或重新配置 或者更一般地说 从它提供跟踪的系统内部禁用或重新配置 Intel PT 是否在写入特定寄
  • x86 程序集 Pushl/popl 不适用于“错误:后缀或操作数无效”

    我是汇编编程的新手 正在努力解决编程基础 http savannah nongnu org projects pgubook 在带有 GNU 汇编器 v2 20 1 的 Ubuntu x86 64 桌面上 我已经能够汇编 链接执行我的代码
  • 为什么 LED 保持亮起而不是闪烁?

    这是使用 pic16f676 中的 TIMER0 中断使 LED 闪烁的 MPASM 代码 端口 A 的引脚 0 RA0 未切换至关闭位置 请帮忙 我是图片组装的新手 我想掌握图片 有没有高手帮我学习一下 我需要以 1 秒的间隔眨眼 代码是
  • 在 qemu 中将扇区加载到 RAM

    我编写了一个简单的程序 将扇区 扇区编号 2 加载到 RAM 但什么也没打印 首先 我尝试了以下引导扇区代码 org 0x7c00 mov ax 0x1000 ES BX 1000 0000 mov es ax mov bx 0x00 Lo
  • 如何构建gcc multilib工具链?

    我正在尝试在新安装的 ubuntu 14 04 的 AMD64 版本上构建 gcc multilib 工具链 它只有 x86 64 gcc 和 g 安装 没有 multilib 支持 我的配置行是 configure disable che
  • 如何在汇编中使用 ReadString?

    mov edx offset Prompt1 call WriteString mov ecx 32 mov edx offset String1 call ReadString 现在 我该如何访问String1 如何将其移入寄存器以便对其
  • 在共享库中不使用 PLT 的情况下调用另一个目标文件中的函数?

    我有两个汇编代码 code1 s and code2 s我想从这两个构建一个可重定位 使用 fPIC 开关 共享库 I want code2 s调用一个函数 名为myfun1 其定义在code1 s 当我使用call myfun1 PLT
  • 将代码保存在 L1 缓存中

    我一直在阅读维基百科关于 K 编程语言的文章 http en wikipedia org wiki K programming language Performance characteristics这就是我所看到的 解释器的小尺寸和语言的
  • 了解汇编语言中的 cmpb 和循环

    我有一个函数 string length 它具有以下汇编代码 0x08048e90 lt 0 gt push ebp 0x08048e91 lt 1 gt mov esp ebp 0x08048e93 lt 3 gt mov 0x8 ebp
  • ARM 汇编:从 STDIN 获取字符串

    我目前正在学习 CS 课程 我们刚刚开始在 Raspberry Pi 上使用 ARM Assembly 事实证明这相当困难 想知道是否有人可以提供帮助 我当前的任务是从 stdin 获取一个字符串 使用 scanf 并计算其中的字符数 然后
  • 英特尔 JCC 勘误表 - 用于缓解的前缀有什么影响?

    Intel 推荐 https www intel com content dam support us en documents processors mitigations jump conditional code erratum pd
  • 为什么锁可以确保底层监视器被释放而直接使用监视器却不能?

    msdn 文章线程同步 C 编程指南 http msdn microsoft com en us library ms173179 28v vs 90 29 aspx规定 lock x DoSomething 相当于 System Obje
  • 将 2 个数字与汇编进行比较[关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我有以下代码 我想完成汇编代码 如下
  • Intel 和 AMD 处理器有相同的汇编程序吗?

    C语言被用来编写Unix以实现可移植性 使用不同编译器编译的同一个C语言程序会产生不同的机器指令 为什么 Windows 操作系统能够在两者上运行Intel https en wikipedia org wiki Intel and AMD
  • MIPS 汇编不支持“.set noat”吗?

    目前 我正在学习GNU as 并在 info as 中找到了很多有用的信息 我发现 set noat 在MIPS指定的代码中使用 但是当在 info as 中搜索该指令时 我在节点 alpha指令 中找到了它的解释 但在 MIPS Depe
  • 如何使用 LOCK ASM 前缀来读取值?

    我知道如何使用 LOCK 来线程安全地递增一个值 lock inc J 但是如何以线程安全的方式读取 J 或任何值 LOCK 前缀不能与 mov 一起使用 如果我执行以下操作 xor eax eax lock add eax J mov J

随机推荐

  • 在数据框中查找每个月的最后一个日期

    我一直在工作中遇到一个问题 我有一个日期数据集 采用数据框格式 我需要搜索并找到每个月的最后一天并将它们放入新的数据框中 下一列中还有一个与之相关的值 这是日期的样本 1 2015 05 21 2015 05 20 2015 05 19 2
  • 使用app sdk登录Rally

    如何使用 app sdk 以编程方式登录 Rally 我在拉力赛之外使用它 它首先重定向到登录页面 我想在没有用户输入凭据的情况下登录 Rally 有一个称为 LoginKey 的工具 可用于帮助对应用程序进行外部身份验证 如果您想在不登录
  • 如何在pygame中上下移动一个盒子[矩形作为正方形]?

    我正在尝试上下移动一个盒子pygame 我可以用钥匙向左移动盒子a并右键使用d 我怎样才能上下移动它 我的代码 import sys import pygame as pg def main screen pg display set mo
  • Vuejs 和数据表:使用 v-for 填充数据时表为空

    我正在尝试使用 vuejs v for 指令和 ajax 来填充数据表来获取数据 但该表始终显示 表中没有可用数据 即使显示了一些数据 并且底部还显示 显示 0 到0 个条目 共 0 个条目 我猜这是因为 vuejs 是反应性的 表格可能无
  • 将页脚置于底部

    我想将页脚放置在页面底部 例如 在内容不多的页面上 我仍然需要底部的页脚 但如果内容长于页面的高度 则必须将页脚向下推 可以仅使用 CSS 来完成还是需要添加一些 jQuery 魔法 您应该使用其中之一粘页脚技巧
  • JUnit 测试用例在 eclipse 中通过,但在 Maven 构建中失败

    我使用 spring 为 JPA 编写了一个 JUnit 测试用例 测试用例在 eclipse 中通过 但是如果我使用 maven mvn test 执行相同的测试用例 它就会失败 我的测试用例是 import javax annotati
  • _IDTExtensibility2,什么DLL导入到ATL项目中?

    所以我正在努力解决我的问题Office 插件再次任务 我已经创建了ATL项目 添加了简单的类 现在想要添加接口实现 如下http www devarticles com c a Cplusplus Writing an MS Word Ad
  • 释放数据库中库存的最佳实践

    我正在构建一个售票应用程序 用于跟踪门票库存 并在特定门票售完时停用它们 我想知道当订单中途放弃时将库存释放回商店的最佳做法是什么 目前的流量 用户添加items to an order as line items和order付款成功后标记
  • 如何动态添加edittext到android

    我想将 edittext 动态添加到 android 显示 我想做一些类似于 Android 联系人的东西 您可以在其中动态添加字段并在不需要时删除它们 感谢您的帮助 动态查看一切 TextView tv new TextView this
  • printf 中 double 的正确格式说明符

    正确的格式说明符是什么double在 printf 中 是吗 f或者是 lf 我相信这是 f 但我不确定 代码示例 include
  • 如何将nif流文件从1.12版本迁移到1.16.3

    我有一个在 NiFi 1 12 0 中运行的数据流 此安装的相关属性如下 nifi sensitive props key nifi sensitive props key protected nifi sensitive props al
  • .net实体框架与oracle 11g

    我正在将实体框架与 Oracle 提供程序 Oracle ManagedDataAccessDTC 一起使用 从 Visual Studio 运行一切正常 但是当我将其发布到 IIS 时 我收到连接错误异常 这是我的 webconfig 女
  • 使用 dplyr 从数据帧中采样子组行

    如果我想从不同组中随机选择一些样本 我使用 plyr 包和下面的代码 require plyr sampleGroup lt function df size df sample nrow df size size iris sample
  • 跟踪 Android 应用程序内的用户空闲时间

    据我所知 没有系统API可供我获取用户空闲时间 当我说用户空闲时间时 我的意思是用户在我的应用程序内的触摸屏上进行一些交互 因此 我想自己跟踪它 我想到的方法是扩展Activity并覆盖onuserinteraction方法来保存最后用户活
  • 图像上传后,React Native 中仅 iOS 的图像 uri 为空

    在我的 React Native 应用程序中 我添加了上传多个图像的功能 这些图像将存储为 image 包括 uri 这对于 Android 来说非常有效 但对于iOS来说 创建的image 也包含一些数据 但与android完全不同 对于
  • 从“.exe”中删除调试信息[重复]

    这个问题在这里已经有答案了 如果我将 C 程序 exe 放入文本编辑器中 我可以在其中找到调试信息 我怎样才能删除它 编辑 我不关心 pdb 文件 我只关心可执行文件中有 pdb 文件的路径 该路径包含我的名字 在本例中是巧合 我的问题是如
  • 如何在.NET Core 3.1中的Newtonsoft JsonConverter中注入依赖

    我无法让依赖注入在 NET Core 3 1 中为以下 Newtonsoft JsonConverter 工作 我只想在属性级别使用它 而不是在全局级别使用它 因此 只有当指定的属性来自某个类时才应执行 JsonConverter publ
  • 如何通过 SQL 将计算列添加到 Access

    如何在 SQL 中向 Access 表添加计算列 我知道我可以使用 SQL 添加一列 如下所示 ALTER TABLE Clients ADD COLUMN AccountDate TEXT 60 谢谢 维托尔 您无法使用 SQL 添加计算
  • Jackson 的 JsonIgnore

    JsonIgnore 注释似乎对我不起作用 有什么想法吗 public class JsonTest implements Serializable JsonIgnore private static JsonTest instance n
  • 在最新的 64 位 Intel CPU 上,如何在没有隐式锁的情况下与寄存器交换堆栈顶部?

    x64 调用约定使用寄存器最多前 4 个参数 rcx rdx r8 r9 并将其余参数传递到堆栈上 在这种情况下 处理补充参数的明显方法是asm程序如下 procedure example param1 rcx param2 rdx par