x86_64 将 64 位寄存器减少到 32 位并保留零或非零状态的最佳方法

2024-03-13

我正在寻找最快/最节省空间的方法,将 64 位寄存器减少为 32 位寄存器,仅保留 64 位寄存器的零/非零状态。

我目前适用于所有价值观的最佳想法是popcntq
(1c tput,主流 Intel 上的 3c 延迟,5 字节代码大小):

// rax is either zero or non-zero
popcntq %rax, %rax
// eax will be zero if rax was zero, otherwise it will be non-zero

注意:仅使用 32 位是不行的eax直接:如果rax有人说2^61的零/非零状态eax不等于rax

有没有更好的巧妙方法?


最少的微指令(前端带宽):
1 uop,延迟 3c (Intel) 或 1c (Zen)。
也是最小的代码大小,5 字节。

 popcnt  %rax, %rax         # 5 bytes, 1 uop for one port
 # if using a different dst, note the output dependency on Intel before ICL

在大多数具有该功能的 CPU 上,延迟为 3c,吞吐量为 1c(一个端口 1 uop)。
或者 Zen1/2/3 上的 1c,吞吐量为 0.25c。 (https://agner.org/optimize/ https://agner.org/optimize/ & https://uops.info/ https://uops.info/)

在挖掘机之前的推土机系列中,popcnt r64是 4c 延迟,4c 吞吐量。 (32 位操作数大小具有 2c 吞吐量,但仍有 4c 延迟。)Bobcat 的微编码 popcnt 相当慢。


最低延迟(假设 Haswell 或更新版本,所以有写入 AL 然后读取 EAX 时无部分寄存器效应 https://stackoverflow.com/questions/45660139/how-exactly-do-partial-registers-on-haswell-skylake-perform-writing-al-seems-to,或者没有 P6 血统的 uarch,不会重命名部分寄存器):
2 个周期延迟、2 个微指令、6 个字节。如果 popcnt (5B) 不可用,也是最小的代码大小。

  test  %rax, %rax     # 3B, 1 uop any ALU port
  setnz %al            # 3B, 1 uop p06 (Haswell .. Ice Lake)
# only works in-place, with the rest of EAX already being known 0 for RAX==0

AL 是 EAX 的低字节,因此 AL=1 肯定会使 EAX 对于任何非零 RAX 都非零。

在 Sandybridge/Ivy Bridge 上读取 EAX 时,这将花费一个合并微指令。 Core2 / Nehalem 将停滞几个周期以插入合并微指令。早期的 P6 系列(例如 Pentium-M)将完全停止运行,直到setcc如果后面的指令读取 EAX,则退出。 (为什么 GCC 不使用部分寄存器? https://stackoverflow.com/questions/41573502/why-doesnt-gcc-use-partial-registers)

Nate's neg/sbb与 Broadwell 及更高版本上的大致相同,但短 1 个字节。(并将上部 32 归零)。这在 Haswell 上更好,其中sbb是 2 uop。在早期的主流 Intel CPU 上,它们都需要 3 uop,其中这个在读取 EAX 时需要合并 uop,或者sbb(以外sbb $0在 SnB/HSW 上)始终需要 2 uop。 neg/sbb 可以用于不同的寄存器(仍然会破坏输入),但对 AMD 以外的 CPU 具有错误的依赖性。 (K8/K10、Bulldozer 系列和 Zen 系列均认可sbb same,same仅取决于 CF)。


如果你想把上面的32归零,BMI2RORX https://www.felixcloutier.com/x86/rorx复制并移动:
2 uop、2c 延迟、8 字节

 rorx  $32, %rax, %rdx      # 6 bytes, 1 uop, 1c latency
 or    %edx, %eax           # 2 bytes, 1c latency
## can produce its result in a different register without a false dependency.

rorx $32通常对于水平 SWAR 缩减很有用,例如对于双字水平总和,你可以movqXMM 寄存器中的一对双字,并使用 rorx/add 而不是 pshufd/paddd 在标量中执行最后的 shuffle+add。

或者没有 BMI2,同时仍将上 32 归零:
Intel 上 7 字节、4 微指令、3c 延迟 (where bswap r64为 2 uops,2c 延迟),否则在高效 CPU 上为 3 uops 2c 延迟bswap r64比如 Zen 家族和 Silvermont 家族。

 mov    %eax, %edx       # 2 bytes, and not on the critical path
 bswap  %rax             # 3 bytes, vs. 4 for  shr $32, %rax 
 or     %edx, %eax       # 2 bytes
## can write a different destination

妥协使用shr $32, %rax代替bswap is
8 字节、3 微指令、2c 延迟对于上述情况,即使没有移动消除。
在原始寄存器上运行 ALU 指令,而不是在movresult 让非消除 MOV 与其并行运行。


评估“最佳”绩效的背景:

  • 每条汇编指令需要多少个CPU周期? https://stackoverflow.com/questions/692718/how-many-cpu-cycles-are-needed-for-each-assembly-instruction/44980899#44980899- 这不是绩效的运作方式; CPU 不会等待一条指令完成后再开始下一条指令,并且重叠的可能性取决于细节。
  • 预测现代超标量处理器上的操作延迟需要考虑哪些因素以及如何手动计算它们? https://stackoverflow.com/questions/51607391/what-considerations-go-into-predicting-latency-for-operations-on-modern-supersca
  • https://agner.org/optimize/ https://agner.org/optimize/指南和说明表
  • https://uops.info/ https://uops.info/机器生成的指令表(微指令、端口、延迟),没有拼写错误。
  • https://stackoverflow.com/tags/x86/info https://stackoverflow.com/tags/x86/info其他链接

没有成功的想法:

   bsf %rax, %rax    # 4 bytes.  Fails for RAX=1

对于输入 = 0,保留目的地不变。 (AMD 记录了这一点,Intel 实现了它,但没有将其记录为面向未来的。)不幸的是,对于输入 1,这会产生 RAX=0。
并且具有相同的性能popcnt在 Intel 上,在 AMD 上更糟,但确实节省了 1 个字节的代码大小。

(Using sub $1设置CF然后??;内特的用法neg就是如何让它干净利落地工作。)

我没有尝试使用超级优化器 https://en.wikipedia.org/wiki/Superoptimization强力检查其他可能的序列,但正如 Nate 评论的那样,这是一个足够短的问题,可以作为一个用例。

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

x86_64 将 64 位寄存器减少到 32 位并保留零或非零状态的最佳方法 的相关文章

  • 一条指令可以同时处于两种寻址模式吗?

    我在书中读到了以下内容从头开始编程 处理器有多种不同的访问数据的方式 称为 寻址模式 最简单的模式是立即模式 其中 要访问的数据嵌入在指令本身中 例如 如果我们想将寄存器初始化为 0 而不是给出 计算机要从中读取 0 的地址 我们将指定立即
  • 遍历内存编辑每个字节

    我正在编写汇编代码 提示用户输入一串小写字符 然后输出包含所有大写字符的相同字符串 我的想法是迭代从特定地址开始的字节 并从每个字节中减去 20H 将小写变为大写 直到到达具有特定值的字节 我对 Assembly 相当缺乏经验 所以我不确定
  • C++ 中的 CPUID 实现

    我想知道这里是否有人有一些可以从任何托管 net 语言引用的 C CPUID 实现的好示例 另外 如果情况并非如此 我是否应该注意 X86 和 X64 之间的某些实现差异 我想使用 CPUID 来获取运行我的软件的机器上的信息 崩溃报告等
  • 即使我确实为变量设置了初始值,数据段也没有被初始化

    我已经编写了一个代码 该代码应该生成某种数字列表 但是即使我为它们分配了初始值 我的数据段变量也没有被初始化 This is how DS 0000 looks when I run it 这是我的代码 但数据段只保留垃圾值 MODEL s
  • orpd等SSE2指令有什么意义?

    The orpd指令是 压缩双精度浮点值的按位逻辑或 这不是做完 全相同的事情吗por 按位逻辑或 如果是这样 拥有它还有什么意义呢 请记住 SSE1orps https www felixcloutier com x86 orps首先 实
  • 在 x86 程序集中将整数打印到控制台

    当我在 16 位汇编中添加两个值时 将结果打印到控制台的最佳方法是什么 目前我有这个代码 CODE START mov ax 1 put 1 into ax add ax 2 add 2 to ax current value mov ah
  • 为什么不能执行 mov [eax], [ebx] [重复]

    这个问题在这里已经有答案了 我可以做这个 mov eax ebx 和这个 mov eax ebx 甚至这个 mov eax ebx 但不是这个 错误C2415 mov eax ebx 只是wtf 为什么 它与 ptr1 ptr2 相同 为什
  • 为什么 Solaris 汇编器生成的机器代码与 GNU 汇编器在这里不同?

    我为 amd64 编写了这个小汇编文件 对于这个问题来说 代码的作用并不重要 globl fib fib mov edi ecx xor eax eax jrcxz 1f lea 1 rax ebx 0 add rbx rax xchg r
  • Visual Studio 2017 上的简单装配程序

    386 model flat c stack 100h printf PROTO arg1 Ptr Byte data msg1 byte Hello World 0Ah 0 code main proc INVOKE printf ADD
  • g++.exe 和 x86_64-w64-mingw32-g++.exe 有什么区别?

    同样的问题也适用于 gcc ar 等 在 Code Blocks 中将工具链可执行文件从 Something exe 更改为 x86 64 w64 mingw32 something exe 时 代码仍然可以完美编译 此外 32 位和 64
  • 为什么 Visual Studio 使用 xchg ax,ax

    我正在查看程序的反汇编 因为它崩溃了 并注意到很多 xchg ax ax 我用谷歌搜索了一下 发现它本质上是一个 nop 但是为什么 Visual Studio 会执行 xchg 而不是 noop 该应用程序是一个C NET3 5 64位应
  • 为什么 RISC-V S-B 和 U-J 指令类型以这种方式编码?

    我正在读一本书 计算机组织与设计RISC V版 我遇到了 S B 和 U J 指令类型的编码 我上面提到的那些类型有奇怪的编码立即字段 S B 类型将直接字段分为两部分 这是有道理的 因为所有指令编码都必须相似 但我无法理解为什么立即字段以
  • 两个基本的 ANTLR 问题

    我正在尝试使用 ANTLR 来获取简单的语法并生成汇编输出 我在 ANTLR 中选择的语言是 Python 许多教程看起来非常复杂或详细阐述与我无关的事情 我真的只需要一些非常简单的功能 所以我有两个问题 将值从一个规则 返回 到另一规则
  • 页面错误陷阱的成本

    我有一个应用程序 它定期 每 1 或 2 秒后 通过分叉自身来获取检查点 因此 检查点是原始进程的一个分支 它一直保持空闲状态 直到原始进程发生某些错误时被要求启动 现在我的问题是fork的写时复制机制的成本有多大 每当原始进程写入内存页面
  • 寄存器寻址模式与直接寻址模式

    我在试卷中遇到过这个问题 它指出 哪种给定的寻址模式更快 为什么 寄存器寻址方式 直接寻址方式 现在根据我的说法 寄存器寻址模式应该更快 因为寄存器是计算机中最快的存储位置 这是正确答案吗 请帮忙 谢谢 两种寻址模式之间的区别是 地址的来源
  • 大会,你好世界问题

    我正在 Linux 上学习 asm noobuntu 10 04 我得到了以下代码 http asm sourceforge net intro hello html http asm sourceforge net intro hello
  • “mov (%ebx,%eax,4),%eax”如何工作? [复制]

    这个问题在这里已经有答案了 一直在从事装配作业 并且在很大程度上我对装配非常了解 或者至少对于这项任务来说足够好 但这个 mov 的声明让我很困惑 如果有人能解释这个 mov 语句如何操作寄存器值 我将非常感激 mov ebx eax 4
  • 程序集比较标志理解

    我正在努力理解汇编程序中的以下代码片段 if EAX gt 5 EBX 1 else EBX 2 在汇编程序中 可以写如下 根据我的书 模拟jge操作说明 https www felixcloutier com x86 jcc您通常会使用
  • 使用 MIPS 从 Big Endian 到 Little Endian 无需逻辑运算?

    我正在使用 MIPS QtSpim 将 32 位字从 Big Endian 转换为 Little Endian 我下面显示的内容已检查且正确 不过我想知道还有什么其他方法可以让我进行转换 我虽然只使用了旋转和移位 但如果没有逻辑运算 我就无
  • 如何将输入传递到扩展汇编中?

    考虑这段代码 来自我的先前的问题 https stackoverflow com questions 37955538 segfault on movq instruction int main asm movq 100000000 rcx

随机推荐

  • 人类可读订单代码的完美哈希函数

    我正在尝试生成从 比方说 无符号 32 位内部 ID 派生的非连续人类可读订单代码 该 ID 从 1 开始 并针对每个新订单自动递增 在下面的示例代码中 每个 hash是独一无二的吗 我计划对 hash使其易于人类阅读
  • Java(14 及以上)instanceof 转换为泛型(例如 List

    我正在使用 Jackson 反序列化 JSON 有效负载 我有一些字段可以是对象或对象数组 所以在我的Java代码中我声明 class Outer private Object inner 我知道内在可以是Map
  • 从 Wikipedia API 中提取数据

    我希望能够使用 json 从维基百科中提取标题和描述 所以 维基百科不是我的问题 我是 json 的新手 想知道如何使用它 现在我知道有数百个教程 但我已经工作了几个小时 它只是不显示任何内容 这是我的代码
  • 如何通过peerJS 点对点连接接收数据?

    我正在尝试使用peerJS 连接两个对等点 我几乎只是在遵循他们的 入门 但我仍然在挣扎 下面是我到目前为止得到的代码
  • Java:从文件加载图像,编辑并添加到 JPanel

    我想将计算机中的图像加载到 2D Graphics 中 以便之后可以对其进行编辑 然后我想将其添加到JPanel 如果您需要查看我的项目 我可以将其发送给您 void loadImage FileDialog fd new FileDial
  • OSX 10.8 xcrun(没有这样的文件或目录)

    执行时gem install jekyll在 OSX 10 8 上安装了来自 Apple 开发人员站点的独立命令行工具包 无 Xcode 时 我遇到以下错误 构建本机扩展 这可能需要一段时间 错误 安装 jekyll 时出错 错误 无法构建
  • SQL 查询限制来自一个表的行,而不是结果集

    我正在运行一个带有连接的简单查询 类似于 SELECT t1 a t2 b FROM t1 LEFT JOIN t2 ON LIMIT 5 As t1有很多行t2 任何大于 2 的数字 LIMIT 语句不会返回前 5 行t1以及来自的相应条
  • const 有什么问题吗?

    已知的不足有哪些const在 C 和 C 0x 中 唯一的问题是const就是这样被严重低估许多开发商 它是 C 工具箱中最好的工具之一 非常锋利 但割伤自己并不危险
  • 禁用的 uibarbuttonitem 的文本颜色始终是正常状态的颜色

    我在我的代码中写道 UIBarButtonItem appearanceWhenContainedIn UINavigationBar class nil setTitleTextAttributes NSForegroundColorAt
  • 限制 Jenkins 仅针对特定分支进行并发构建

    我一直在寻找如何做到这一点一段时间 但我开始认为这是不可能的 与此类似的每个问题似乎都与限制某些节点或整个管道的运行更相关 我们在 Jenkins 中有一个多分支管道 用于处理 PR 构建 分阶段测试和生产发布 三者中的每一个都通过when
  • VC++ 缺少类型说明符 - 假定为 int。注意:C++ 不支持default-int [重复]

    这个问题在这里已经有答案了 以下代码在 VC 6 0 中编译得很好 但是当我在 VS2005 中打开该项目时 出现此错误 BCGPopupMenu h 100 错误 C4430 缺少类型说明符 int 假定 注意 C 不支持default
  • 为可移植类库创建 NuGet 包时出错

    我是 Nuget 新手 我正在尝试为可移植类库项目创建一个 NuGet 包 该项目对我创建的另一个可移植类库具有包依赖性 我遇到了一个问题 在调用时收到错误块包对于项目文件测试组件2 csproj 我从 VS 2013 中的 Portabl
  • &(单个与符号)和 | 是什么意思(单管)运算符进行比较运算?

    在这样的声明中 if valueA valueB 99 print they don t equal 99 什么是 运营商做什么 我认为这意味着如果 valueS 和 value 都不等于 99 则执行该块 然后什么是 在此声明中执行以下操
  • 在zf2中配置多个数据库

    如何在 Zend Framework 2 中配置 和使用 多个数据库 目前我的 global php 中有这个 return array db gt array driver gt Pdo dsn gt mysql dbname my db
  • Pythoncoverage.py 排除行

    背景 我有几个使用coverage py 的Django 项目 并且一直在尝试向我的 coveragerc 配置文件的exclude lines 部分添加一些额外的表达式 问题是 即使使用正确的正则表达式 也可以在测试器中拾取该行 例如ht
  • 删除字符串中的所有 HTML 标签(使用 jquery text() 函数)

    是否可以使用 jquery text 函数删除字符串中的所有 HTML 带有 HTML 标签的字符串 myContent div Hello span world span div 结果必须是 Hello world var myConte
  • C# 中字符串类型最快(内置)比较是什么

    C 中字符串类型最快的内置比较方法是什么 我不介意印刷 语义含义 目的是在排序列表中使用比较器以便在大型集合中快速搜索 我认为只有两种方法 Compare and CompareOrdinal 什么是最快的 此外 是否有更快的字符串比较方法
  • 拍照后编辑图像

    我目前正在制作一个 iPhone 应用程序 用户可以在其中拍照或从相册中选择照片 然后将覆盖层放置在图像上 然后用户可以缩放 旋转和保存图像 目前 我可以拍照 或者选择一张放入相册 至于覆盖层 我只是使用 UIImageView 并将其放置
  • AngularJS:AppLevel 控制器可能吗?

    我有一个控制器 它是我的页面的控制器 但我想知道是否有可能有一个 AppLevel 控制器 即可以从每个页面访问的东西 所以每个页面实际上会分配超过 1 个控制器 我知道我可能可以通过服务来做到这一点并注入服务 但我希望有某种可以分配的应用
  • x86_64 将 64 位寄存器减少到 32 位并保留零或非零状态的最佳方法

    我正在寻找最快 最节省空间的方法 将 64 位寄存器减少为 32 位寄存器 仅保留 64 位寄存器的零 非零状态 我目前适用于所有价值观的最佳想法是popcntq 1c tput 主流 Intel 上的 3c 延迟 5 字节代码大小 rax