什么时候汇编比C更快? [关闭]

2024-05-03

了解汇编程序的既定原因之一是,有时可以使用它来编写比使用高级语言(尤其是 C)编写的代码性能更高的代码。然而,我也多次听到它说,虽然这并不完全错误,但汇编程序可以的情况actually用于生成性能更高的代码的情况极为罕见,并且需要汇编方面的专业知识和经验。

这个问题甚至没有涉及汇编程序指令将是机器特定的且不可移植的事实,或汇编程序的任何其他方面。当然,除了这个之外,了解汇编还有很多充分的理由,但这只是一个征求示例和数据的具体问题,而不是关于汇编程序与高级语言的扩展讨论。

谁能提供一些具体例子在许多情况下,使用现代编译器进行汇编比编写良好的 C 代码更快,您能用分析证据来支持这一说法吗?我非常有信心这些案例的存在,但我真的很想知道这些案例到底有多深奥,因为这似乎是一些争论的焦点。


这是一个现实世界的示例:旧编译器上的定点乘法。

这些不仅在没有浮点的设备上很方便,而且在精度方面也很出色,因为它们为您提供 32 位精度和可预测的误差(浮点数只有 23 位,并且更难以预测精度损失)。即制服absolute整个范围内的精度,而不是接近均匀relative精确 (float).


现代编译器很好地优化了这个定点示例,因此对于仍然需要特定于编译器的代码的更现代示例,请参阅

  • 获取 64 位整数乘法的高位部分 https://stackoverflow.com/questions/28868367/getting-the-high-part-of-64-bit-integer-multiplication/50958815#50958815:便携式版本使用uint64_t对于 32x32 => 64 位乘法无法在 64 位 CPU 上进行优化,因此您需要内在函数或__int128在 64 位系统上实现高效代码。
  • Windows 32 位上的 _umul128 https://stackoverflow.com/questions/46870373/umul128-on-windows-32-bits:在将 32 位整数乘以 64 时,MSVC 并不总是做得很好,因此内在函数有很大帮助。

C 没有全乘运算符(N 位输入的 2N 位结果)。用 C 语言表达它的通常方法是将输入转换为更宽的类型,并希望编译器认识到输入的高位不重要:

// on a 32-bit machine, int can hold 32-bit fixed-point integers.
int inline FixedPointMul (int a, int b)
{
  long long a_long = a; // cast to 64 bit.

  long long product = a_long * b; // perform multiplication

  return (int) (product >> 16);  // shift by the fixed point bias
}

这段代码的问题在于我们做了一些无法直接用 C 语言表达的事情。我们想要将两个 32 位数字相乘并得到 64 位结果,我们返回其中的中间 32 位。然而,在 C 语言中,这种乘法并不存在。您所能做的就是将整数提升为 64 位并进行 64*64 = 64 乘法。

然而,x86(以及 ARM、MIPS 等)可以在单个指令中执行乘法。一些编译器过去常常忽略这一事实,并生成调用运行时库函数来执行乘法的代码。移位 16 也通常由库例程完成(x86 也可以进行此类移位)。

因此,我们只剩下一两个库调用来进行乘法运算。这会产生严重的后果。不仅移位速度较慢,而且必须在函数调用之间保留寄存器,而且它也无助于内联和代码展开。

如果您在(内联)汇编器中重写相同的代码,您可以获得显着的速度提升。

除此之外:使用ASM并不是解决问题的最好方法。大多数编译器允许您以内在形式使用一些汇编指令(如果您无法用 C 语言表达它们)。例如,VS.NET2008 编译器将 32*32=64 位 mul 公开为 __emul,将 64 位移位公开为 __ll_rshift。

使用内在函数,您可以以 C 编译器有机会了解发生了什么的方式重写函数。这允许内联代码、分配寄存器、消除公共子表达式和常量传播。你会得到一个huge通过这种方式,可以提高手写汇编代码的性能。

供参考:VS.NET 编译器的定点 mul 的最终结果是:

int inline FixedPointMul (int a, int b)
{
    return (int) __ll_rshift(__emul(a,b),16);
}

定点除法的性能差异甚至更大。通过编写几行汇​​编代码,我将重除法的定点代码改进了 10 倍。


使用 Visual C++ 2013 为两种方式提供相同的汇编代码。

2007年的gcc4.1也很好地优化了纯C版本。 (Godbolt 编译器资源管理器没有安装任何早期版本的 gcc,但想必即使是较旧的 GCC 版本也可以在没有内在函数的情况下执行此操作。)

请参阅 x86(32 位)和 ARM 的源代码 + asmGodbolt 编译器浏览器 https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,lang:c%2B%2B,source:'//+compiled+with+gcc+-mregparm+so+args+are+in+regs,+like+when+inlining%0A//+Remove+-m32+to+see+x86-64+code-gen%0A%0A//+static+inline%0Aint+FixedPointMul+(int+a,+int+b)+%7B%0A++long+long+a_long+%3D+a%3B+//+cast+to+64+bit.%0A++long+long+product+%3D+a_long+*+b%3B+//+perform+multiplication%0A++return+(int)+(product+%3E%3E+16)%3B++//+shift+by+the+fixed+point+bias%0A%7D%0A%0A//+Modern+compilers+know+that+32-bit+integers+cast+to+64%0A//+still+only+have+32+significant+bits,%0A//+so+one+32-bit+signed+multiply+is+sufficient%0A%0A%23ifdef+_MSC_VER%0A%23include+%3Cintrin.h%3E%0A//+static+inline%0Aint+FixedPointMul_msvc+(int+a,+int+b)+%7B%0A++++return+(int)+__ll_rshift(__emul(a,b),16)%3B%0A%7D%0A%23endif%0A%0A%0A/*+Intrinsics+are+more+useful+for+extended+precision%0A+*+when+there+isn!'t+a+wide-enough+type.%0A+*+e.g.+128-bit+integer+on+compilers+without+__int128%0A+*/%0A'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:32.75251522372254,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((g:!((h:compiler,i:(compiler:g412,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'1',trim:'1'),lang:c%2B%2B,libs:!(),options:'-xc+-O3+-m32++-fomit-frame-pointer',source:1),l:'5',n:'0',o:'x86-64+gcc+4.1.2+(Editor+%231,+Compiler+%231)+C%2B%2B',t:'0')),k:34.10775747948107,l:'4',m:50,n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:arm710,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),lang:c%2B%2B,libs:!(),options:'-xc+-O3+-mthumb+-mcpu%3Dcortex-m4',source:1),l:'5',n:'0',o:'ARM+gcc+7.2.1+(none)+(Editor+%231,+Compiler+%232)+C%2B%2B',t:'0')),header:(),l:'4',m:50,n:'0',o:'',s:0,t:'0')),k:33.91415144294414,l:'3',n:'0',o:'',t:'0'),(g:!((g:!((h:compiler,i:(compiler:clang30,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),lang:c%2B%2B,libs:!(),options:'-xc+-O3+-m32',source:1),l:'5',n:'0',o:'x86-64+clang+3.0.0+(Editor+%231,+Compiler+%233)+C%2B%2B',t:'0')),k:33.33333333333333,l:'4',m:50,n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:cl19_2015_u3_32,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),lang:c%2B%2B,libs:!(),options:'-Ox',source:1),l:'5',n:'0',o:'x86+MSVC+19+2015+U3+(Editor+%231,+Compiler+%234)+C%2B%2B',t:'0')),header:(),l:'4',m:50,n:'0',o:'',s:0,t:'0')),k:33.33333333333333,l:'3',n:'0',o:'',t:'0')),l:'2',n:'0',o:'',t:'0')),version:4。 (不幸的是,它没有足够老的编译器来从简单的纯 C 版本生成错误的代码。)


现代 CPU 可以做 C 语言没有运算符可以完成的事情at all, like popcnt或位扫描以查找第一个或最后一个设置位。 (POSIX 有一个ffs()函数,但其​​语义与 x86 不匹配bsf / bsr. See https://en.wikipedia.org/wiki/Find_first_set https://en.wikipedia.org/wiki/Find_first_set).

某些编译器有时可以识别一个循环,该循环计算整数中设置的位数并将其编译为popcnt指令(如果在编译时启用),但使用起来更可靠__builtin_popcnt在 GNU C 中,或者在 x86 上(如果您仅针对具有 SSE4.2 的硬件):.

或者在 C++ 中,分配给std::bitset<32>并使用.count()。 (在这种情况下,该语言找到了一种通过标准库可移植地公开 popcount 的优化实现的方法,这种方式始终会编译为正确的内容,并且可以利用目标支持的任何内容。)另请参阅https://en.wikipedia.org/wiki/Hamming_weight#Language_support https://en.wikipedia.org/wiki/Hamming_weight#Language_support.

相似地,ntohl可以编译为bswap(用于字节序转换的 x86 32 位字节交换)在某些具有它的 C 实现上。


内在函数或手写汇编的另一个主要领域是使用 SIMD 指令进行手动矢量化。编译器对于简单的循环来说还不错,比如dst[i] += src[i] * 10.0;,但当事情变得更复杂时,通常会做得很糟糕或者根本不自动矢量化。例如,你不太可能得到类似的东西如何使用SIMD实现atoi? https://stackoverflow.com/questions/35127060/how-to-implement-atoi-using-simd由编译器根据标量代码自动生成。

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

什么时候汇编比C更快? [关闭] 的相关文章

随机推荐

  • Azure 角色配置管理

    当您别无选择只能在 web config 或 app config 中保存配置设置时 我不明白 Windows Azure 如何让您改变应用程序的配置 例如 项目经常会使用大量使用 web config 的第三方库 web config 的
  • 如何读取未知数量的输入?

    我正在使用 C Primer 这本书学习 C In 第1 4 3节 给出了以下关于读取未知数量的输入的示例代码 include
  • 理解Python中的元类和继承[重复]

    这个问题在这里已经有答案了 我对元类有一些困惑 具有继承性 class AttributeInitType object def init self kwargs for name value in kwargs items setattr
  • 二维几何:如何检查点是否在角度内

    我有以下二维几何问题 我有一个点 从该点投射一个无限角度 2D 锥体 该角度由方向和角度给出 该点和方向形成一个向量 并且角度的每一侧一半形成 2D 锥体 现在我想检查 2D 中的另一个点是在这个圆锥体内部还是外部 如何才能实现这一目标 谢
  • 我可以采取什么方法使用 Web 技术开发桌面应用程序

    我想知道是否有任何方法可以创建一个作为桌面应用程序启动的应用程序 供个人使用 由于桌面应用程序可以使用我的 HDD 文件 我可能想要管理这些文件 但觉得将它们扔到网络服务器上并不安全 但我认为我将来可能想做的是将应用程序移植到网络上 因为我
  • WPF 数据绑定和级联转换器?

    我想知道使用 wpf 数据绑定时是否可以级联转换器 例如就像是
  • 预编译 Rails 5 以包含所有资源

    我正在尝试预编译所有资产以进行生产 当我跑步时RAILS ENV production bundle exec rake assets precompile并非我的所有资源都经过预编译 我尝试过使用其他人在其他类似问题中建议的方法 但它们对
  • 如何从主体上的 onClick 事件获取鼠标单击的绝对位置?

    我试图获取鼠标单击相对于浏览器 主体的绝对位置 顶部和左侧 not主体内的任何父元素 我有一个绑定到 body 的侦听器 但 e pageX 和 e pageY 为我提供了相对于 div 的位置 请注意 我可以利用 jQuery 和 YUI
  • 通过 Instagram API 访问公共 Instagram 内容,且 accesstoken 不会过期

    我想显示来自 Instagram 的与特定主题标签相关的公共内容 一切正常 但我无法更新access token每次过期的时候 不要假设您的 access token 永远有效 https www instagram com develop
  • ASP.NET中如何获取当前域名

    我想在asp net c 中获取当前域名 我正在使用这段代码 string DomainName HttpContext Current Request Url Host 我的网址是localhost 5858但它只返回localhost
  • ggplot geom_hline 中无法识别数据列

    我想知道为什么变量mean y不被我识别geom hline yintercept unique mean y call library tidyverse set seed 20 n groups lt 2 n in group lt 2
  • 为什么我们在Java中使用接口引用类型? [复制]

    这个问题在这里已经有答案了 我即将参加第一门面向对象编程课程的期末考试 但我仍然不明白多态性的概念 假设我有一个抽象类 Vehicle 该类有一个名为 Aircraft 的子类 我的问题是 这两个代码有什么不同 Aircraft Jetpl
  • Android SDK 中缺少 Gradle(使用 cordova + ionic)

    是的 我搜索了这个 但没有搜索到任何内容 只是有关 ANDROID HOME 路径的信息 但就我而言 我认为这不是真正的问题 当我尝试做的时候ionic build android我收到这个错误 错误 在 android sdk 中找不到
  • c# 错误:无法识别的配置部分 userSettings - 停止程序读取 user.config?

    在我的 C 表单应用程序 在 VS2010 中使用 NET 4 0 创建 中 我使用应用程序设置来存储输出路径 以前此设置是使用用户范围配置的 但我必须将其范围更改为应用程序 执行此操作后 我现在在表单启动时收到无法识别的配置部分 user
  • iOS:如何创建并绘制(并保存)大于屏幕的图像?

    我们正在创建一个 iOS 照片应用程序 为此 我们必须创建动态大小的图像 最大约为 2500x1600 像素 创建此图像后 我们希望以相当快的速度在大图像之上绘制较小的图像 正如我们所看到的 问题在于不可能获得大于屏幕分辨率的上下文 该调用
  • 如何将“从文件夹打开网站”转换为基于 csproj 的“ASP.NET Web 应用程序”项目

    标题几乎说明了一切 我有一个已构建并签入 vss 的 网站 要打开它 我们将文件 gt 打开 gt 网站 然后选择文件夹 这种技术可以让您获得一个项目 但没有 cs vb proj 文件 有没有简单的方法可以将其转换为 ASP NET We
  • Python lambda 函数没有在 for 循环中正确调用[重复]

    这个问题在这里已经有答案了 我正在尝试使用 Python 中的 Tkinter 制作一个计算器 我使用 for 循环来绘制按钮 并且尝试使用 lambda 函数 以便仅在按下按钮时调用按钮的操作 而不是在程序启动时立即调用 然而 当我尝试这
  • 使用 Spring boot CrudRepository 过滤数据

    我有一个简单的 REST 服务 可以使用 Spring boot 访问数据CrudRepository 该存储库已经实现了分页和排序功能 如下所示 public interface FlightRepository extends Crud
  • 无需 JavaScript 的图像翻转

    我试图找到一种不使用 javascript 的方法 该方法允许您将鼠标悬停在较小的 div 或图像 上来更改较大 div 的背景 纯粹使用 HTML 和 CSS 可以吗 该示例有 2 个问题 1 仅滚动其中一个 div 有效 因为它紧随其后
  • 什么时候汇编比C更快? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 Locked 这个问题及其答案是locked help locked posts因为这个问题是题外话 但却具有历史意义 目前不接受新的