非本机长度的有符号和无符号整数的性能差异

2024-01-13

有这样一段话,CppCon 2016:钱德勒·卡鲁斯(Chandler Carruth)“垃圾进,垃圾出:争论未定义的行为......” https://youtu.be/yG1OZ69H_-o?t=39m16s,其中 Carruth 先生展示了 bzip 代码的示例。他们用过uint32_t i1作为索引。在 64 位系统上,数组访问block[i1]然后会做*(block + i1)。问题是block是一个 64 位指针,而i1是一个32位数字。加法可能会溢出,并且由于无符号整数已定义溢出行为,因此编译器需要添加额外的指令以确保即使在 64 位系统上也确实满足了这一要求。

我还想用一个简单的例子来展示这一点。所以我尝试过++i具有各种有符号和无符号整数的代码。以下是我的测试代码:

#include <cstdint>

void test_int8() { int8_t i = 0; ++i; }
void test_uint8() { uint8_t i = 0; ++i; }

void test_int16() { int16_t i = 0; ++i; }
void test_uint16() { uint16_t i = 0; ++i; }

void test_int32() { int32_t i = 0; ++i; }
void test_uint32() { uint32_t i = 0; ++i; }

void test_int64() { int64_t i = 0; ++i; }
void test_uint64() { uint64_t i = 0; ++i; } 

With g++ -c test.cpp and objdump -d test.o我得到像这样的装配清单 这:

000000000000004e <_Z10test_int32v>:
  4e:   55                      push   %rbp
  4f:   48 89 e5                mov    %rsp,%rbp
  52:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
  59:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
  5d:   90                      nop
  5e:   5d                      pop    %rbp
  5f:   c3                      retq   

说实话:我对 x86 汇编的了解相当有限,所以我的以下内容 结论和问题可能非常幼稚。

前两条指令似乎只是来自函数的调用, 最后三个似乎是返回值。仅删除这些行, 以下内核适用于各种数据类型:

  • int8_t:

       4:   c6 45 ff 00             movb   $0x0,-0x1(%rbp)
       8:   0f b6 45 ff             movzbl -0x1(%rbp),%eax
       c:   83 c0 01                add    $0x1,%eax
       f:   88 45 ff                mov    %al,-0x1(%rbp)
    
  • uint8_t:

      19:   c6 45 ff 00             movb   $0x0,-0x1(%rbp)
      1d:   80 45 ff 01             addb   $0x1,-0x1(%rbp)
    
  • int16_t:

      28:   66 c7 45 fe 00 00       movw   $0x0,-0x2(%rbp)
      2e:   0f b7 45 fe             movzwl -0x2(%rbp),%eax
      32:   83 c0 01                add    $0x1,%eax
      35:   66 89 45 fe             mov    %ax,-0x2(%rbp)
    
  • uint16_t:

      40:   66 c7 45 fe 00 00       movw   $0x0,-0x2(%rbp)
      46:   66 83 45 fe 01          addw   $0x1,-0x2(%rbp)
    
  • int32_t:

      52:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
      59:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
    
  • uint32_t:

      64:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
      6b:   83 45 fc 01             addl   $0x1,-0x4(%rbp)
    
  • int64_t:

      76:   48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
      7d:   00 
      7e:   48 83 45 f8 01          addq   $0x1,-0x8(%rbp)
    
  • uint64_t:

      8a:   48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
      91:   00 
      92:   48 83 45 f8 01          addq   $0x1,-0x8(%rbp)
    

比较签名和未签名的版本,我本希望先生能做到这一点。 Carruth 谈到会生成额外的屏蔽指令。

But for int8_t我们加载一个字节(movb) into %rbp,然后加载并补零 到长(movzbl) 进入累加器%eax。添加(add) 是 由于未定义溢出,因此在没有任何大小规范的情况下执行 反正。无符号版本直接使用字节指令。

两者都可以add and addb/addw/addl/addq取相同数量的 周期(延迟),因为 Intel Sandy Bridge CPU 具有针对所有周期的硬件加法器 大小或 32 位单元在内部进行掩码,因此具有更长的长度 潜伏。

我寻找了一个有延迟的表并发现了一个由 agner.org http://www.agner.org/optimize/instruction_tables.pdf。那里是为了 每个CPU(这里使用Sandy Bridge)只有一个条目ADD但是我愿意 没有看到其他宽度变体的条目。这Intel 64 和 IA-32 架构优化参考手册 https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf似乎也只列出了一个add操作说明。

这是否意味着在 x86 上++i非本机长度整数实际上是 对于无符号类型更快,因为指令更少?


这个问题有两个部分:钱德勒关于基于未定义的溢出的优化的观点,以及您在汇编输出中发现的差异。

钱德勒的观点是,如果溢出是未定义的行为,那么编译器可以假设它不会发生。考虑以下代码:

typedef int T;
void CopyInts(int *dest, const int *src) {
    T x = 0;
    for (; src[x]; ++x) {
        dest[x] = src[x];
    }
}

在这里,编译器可以安全地更改for循环到以下内容:

    while (*src) {
        *dest++ = *src++;
    }

这是因为编译器不必担心以下情况x溢出。如果编译器必须担心x溢出,源指针和目标指针突然减去 16 GB,因此上面的简单转换将不起作用。

在汇编级别,上面是(对于 x86-64 的 GCC 7.3.0,-O2):

_Z8CopyIntsPiPKii:
  movl (%rsi), %edx
  testl %edx, %edx
  je .L1
  xorl %eax, %eax
.L3:
  movl %edx, (%rdi,%rax)
  addq $4, %rax
  movl (%rsi,%rax), %edx
  testl %edx, %edx
  jne .L3
.L1:
  rep ret

如果我们改变T to be unsigned int,我们得到这个较慢的代码:

_Z8CopyIntsPiPKij:
  movl (%rsi), %eax
  testl %eax, %eax
  je .L1
  xorl %edx, %edx
  xorl %ecx, %ecx
.L3:
  movl %eax, (%rdi,%rcx)
  leal 1(%rdx), %eax
  movq %rax, %rdx
  leaq 0(,%rax,4), %rcx
  movl (%rsi,%rax,4), %eax
  testl %eax, %eax
  jne .L3
.L1:
  rep ret

在这里,编译器保留x作为一个单独的变量,以便正确处理溢出。

您可以使用与指针大小相同的大小类型,而不是依赖未定义的有符号溢出来提高性能。这意味着这样的变量只能与指针同时溢出,而指针也是未定义的。因此,至少对于 x86-64,size_t也将作为T以获得更好的性能。

现在回答你问题的第二部分:add操作说明。上的后缀add指令来自所谓的“AT&T”风格的 x86 汇编语言。在 AT&T 汇编语言中,参数与 Intel 编写指令的方式相反,并且通过在助记符中添加后缀来消除指令大小的歧义,而不是像这样dword ptr在英特尔案中。

Example:

Intel: add dword ptr [eax], 1

美国电话电报公司:addl $1, (%eax)

这些是相同的指令,只是写法不同。这l取代dword ptr.

如果 AT&T 指令中缺少后缀,这是因为它不是必需的:大小是从操作数中隐含的。

add $1, %eax

The l后缀是不必要的,因为该指令显然是32位的,因为eax is.

简而言之,它与溢出无关。溢出始终在处理器级别定义。在某些架构上,例如使用非uMIPS上的指令,溢出抛出异常,但仍然defined。 C/C++ 是唯一使溢出行为变得不可预测的主要语言。

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

非本机长度的有符号和无符号整数的性能差异 的相关文章

随机推荐

  • 您可以将存档文件直接添加到 XCode Organizer 吗?

    我有一个朋友为我制作了一个存档 我现在想将其提交到 itunesconnect 有什么方法可以将此存档添加到 XCode 的管理器中 还是我必须使用工作区文件来烘焙自己的存档 谢谢你 双击存档会将其添加到管理器中 我尝试拖入管理器 但它不适
  • 带有 xapian 引擎的 Django-haystack:如果模型具有 ManyToManyField,则无法执行 update_index

    将 django 升级到 1 7 后 我无法执行管理命令 update index Traceback most recent call last File opt pycharm 3 4 1 helpers pydev pydevd py
  • 批量命令冲突

    我制作了这个批处理脚本 它允许用户输入网站的 URL 以及以分钟为单位的时间 然后将 URL 添加到主机文件中 并在时间到期后将其删除 在一定时间内有效屏蔽某个网站 它在首次运行时通过创建另一个批处理文件从hosts文件中删除网站 然后使用
  • yml docker-compose 错误映射值在这里不允许

    我尝试了解容器 但我的 docker compose yml 文件有问题 在运行 docker compose up 后 我总是遇到相同的错误 错误 yaml scanner ScannerError 不允许映射值 这里 即使我更改了 do
  • 水平连接字符串元胞数组

    我希望水平连接字符串元胞数组的行 如下所示 start hello world test join me please finish helloworldtest joinmeplease 是否有任何内置函数可以完成上述转换 有一种简单的非
  • Spring Boot:部署到外部服务器时如何设置异步超时

    在使用嵌入式 tomcat 部署我的 spring boot 应用程序时 我设置异步超时如下 Bean public EmbeddedServletContainerFactory servletContainer TomcatEmbedd
  • Ruby 中非对象的“事物”示例

    一切都是对象 是我学到的关于 Ruby 的第一件事之一 但在 Peter Cooper 的书中Ruby 入门 从新手到专业人士 其中提到 almostRuby 中的一切都是对象 你能给我一些吗示例things那些不是 Ruby 中的对象 我
  • Gulp + babelify + browserify 问题

    我正在尝试使用 browserify 和 babelify 创建一个 gulp 任务 这是任务 var gulp require gulp var browserify require gulp browserify var source
  • 根据列值连接不同的表

    我有一个表R 其中包含表P的外键 在表P中有一列 告诉我表P中的记录是什么类型 与 P type 列中的可能值相关 根据表存在 因此 如果 P type 中的值为 C 则表示名为 C 的表 如果值为 D 则表示名为 D 的表 现在我想要一个
  • 文件的 listFiles() 不适用于符号链接?

    我有以下文件对象通过符号链接指向目录 File directory new File path symlink foo bar String files directory listFiles listFiles 返回 null 这是因为符
  • 如何循环加载所有用户的注册表配置单元

    使用管理员权限 我需要枚举 Windows 7 系统上的所有用户 甚至是已注销的用户 然后我需要为每个用户加载注册表配置单元并设置一个密钥 NetUserEnum 给我 SID 我猜 LsaEnumerateLogonSessions 也会
  • 在 Windows 上我应该将 Eclipse 安装到哪个文件夹?

    我运行的是 Windows 7启用UAC 我一直觉得很奇怪Eclipse http www eclipse org 不使用安装程序 也不使用 AppData 文件夹在 Windows 中存储其数据 但最近我不得不重新安装几次 硬盘驱动器问题
  • Python tkinter 文本修改回调

    在 python 2 7 中 每次 Tkinter Text 小部件中发生更改时 我都尝试获取回调 该程序使用基于此处找到的代码的多个框架 在 tkinter 中的两个框架之间切换 https stackoverflow com quest
  • wpf:获取组合框值

    我有一个名为 cbFileSize 的 WPF 组合框 我尝试获取所选值 如下所示 string tmp cbFileSize SelectedValue ToString MessageBox Show tmp 但 tmp 设置为 Sys
  • 无法加载数据源的类:com.databricks.spark.csv

    My build sbt文件有这个 scalaVersion 2 10 3 libraryDependencies com databricks spark csv 2 10 1 1 0 我正在独立集群模式下运行 Spark 我的 Spar
  • 有条件地应用 Angular 4 中的点击事件

    是否可以在模板中定义一个附加点击处理程序的条件 例如 我能得到的最接近的是评估单击方法入口处的条件 a class user a 如果标志有的话 有没有一种方法可以避免完全绑定到单击事件isOverflown是假的吗 另外 我不想使用ng
  • 使用 Maven 打包并运行 Scala Spark 项目

    我正在 Scala 中编写一个应用程序 它使用Spark http spark apache org 我正在使用 Maven 打包应用程序 并在构建应用程序时遇到问题 uber 或 fat 罐子 https stackoverflow co
  • 在后台处理ViewExpiredException并恢复表单值

    是否有一个无数据库 primefaces 和 keep session alive 的解决方案来防止或在恢复表单输入时在后台静默处理 ViewExpiredException 例如 具有 保持登录 cookie 的用户不希望被重定向到某种错
  • 在 R 中执行时间序列的 fft

    我想使用 FFT 将波拟合到时间序列 目标是绘制具有不同谐波的图 并用它来预测 n 个数据点 我正在使用的代码基于此answer https stackoverflow com questions 41435777 perform four
  • 非本机长度的有符号和无符号整数的性能差异

    有这样一段话 CppCon 2016 钱德勒 卡鲁斯 Chandler Carruth 垃圾进 垃圾出 争论未定义的行为 https youtu be yG1OZ69H o t 39m16s 其中 Carruth 先生展示了 bzip 代码