编程与软件工程danluu.com
手写汇编在速度与简洁性上胜过 intrinsics
Hand-Coded Assembly Beats Intrinsics in Speed and Simplicity
2014 — 2021,它一次次回到首页
黑点是上首页(≥ 50 分)的那次投稿,空心点是没冲上去的投稿,点越大分数越高。
导读
Dan Luu 通过 popcnt 等基准测试对比手写汇编与编译器 intrinsic/编译器输出的性能,发现手写汇编往往更快也更简单,并分析了编译器优化不到位的种种原因。文章还讨论了不同编译器(GCC、Clang、ICC)生成代码质量的差异。
为什么经久不衰:它用具体数据挑战了'编译器生成的代码总是比手写汇编好'这一流行迷思,对关心底层性能的开发者至今仍有参考价值。
HN 评论精选
'手写汇编必然更慢'是过时迷思,现代 CPU 架构早已不是原因
我想是不是该破除'现代 CPU 上人写的汇编比编译器生成的慢'这个迷思了?据我所知这在奔腾时代算是半真——那种非对称双流水线架构有很多配对例外和微码指令,针对上一代 CPU 手工优化的代码很可能用不满第二条流水线。但早在 Pentium Pro 就已有健壮的前端,不需要反直觉的指令重排也能利用多个执行单元。如今还说手工编码比不过编译器,就像说打孔卡上打孔太多会把卡片弄裂一样荒谬。
pandaman查看英文原文
I wonder if it's already the time to put to rest the myth that the human-coded assembly for a modern CPU is slower than what a compiler generates? As far as I know it's been semi-true around Pentium time. On that asymetrical dual pipeline architecture with many parring exceptions and some microcoded instructions in the mix the typical hand-optimized code for the previous generation CPUs would likely under-utilize the second pipe and hit some microcode for a good measure. However, even the Pentium Pro already had a very robust front end that did not require non-intuitive op rearrangement to utilize multiple exec units. Saying that you cannot hand code better than the compiler for a non-Pentium CPU nowadays sounds similar to saying that if you use too many set bits in your data then the punch cards will warp and tear apart from too many holes.
编译器太'聪明'反而碍事,需要一种强制按程序员意图生成代码的模式
'至少在 clang 3.4 里是这样,GCC 4.8.2 太聪明了,把分离目标寄存器的写法又'优化'回原来的版本。'这段话让我深有共鸣。我花了大量时间琢磨怎么骗过各种编译器,在没有内联汇编的着色器场景尤其关键。有些情况下'隐藏信息'反而能让代码跑得更快,比如把编译期常量改成非常量——这算什么 sane 的世界?真希望每个编译器都有'闭嘴,我知道我在干什么'模式,把变量一一映射到寄存器。这不常用,但要从理论性能的 40-50% 提到 90% 以上就必不可少,在游戏等软实时应用里这可能就是 30 帧和 60 帧的差别。
sharpneli查看英文原文
"Well, at least in clang (3.4). Gcc (4.8.2) is too smart to fall for this separate destination thing and “optimizes” the code back to something like our original version." This resonated quite heavily with me. I've spent rather large amount of time figuring out how to trick various compilers. This is especially critical in cases where there is no inline assembly, such as shaders. It's really sad how in some cases you can 'optimize' code by hiding information. In what sane world code runs faster if you make something not a compile time constant as an example? I really wish that every compiler would have "Shut up! I know what I'm doing!" mode where it would basically do what you say as close as possible. 1:1 mapping on registers to certain variables and the few temp regs required for evaluating expressions. This is not needed often but it's absolutely mandatory for going from 40-50% of theoretical speed to >90%. And in games and other soft real time applications this can spell the difference between 30 and 60 fps.
问题根源是编译器对极致性能优化不足,而非 intrinsic 本身
我觉得问题其实不在 intrinsic 本身,而在于编译器对最后 30-50% 性能的优化就是不够好。用 intrinsic 时容易注意到这一点,因为那时你多半在专注性能;但仔细看非 intrinsic 代码也会发现同样的问题:糟糕的寄存器分配导致多余的移动和溢出、没利用算术运算已设置的标志位、别扭的代码布局带来多余的跳转分支。这显然不是编译器作者愚蠢,而是要针对一个目标优化又不损害另一个目标实在太难。不同编译器输出差异很大,说明改进空间不小。我很希望能有办法'锁定'某个函数的输出,而不必受制于各编译器的 whims。
nkurz查看英文原文
I feel like the problem isn't really with intrinsics. Rather, the problem is that compilers just aren't very good at optimizing for the last 30-50% of performance. One notices this when using intrinsics because if you are using them you probably are concentrating on performance. But if you were to look closely at the non-intrinsic code, I think you'd see a lot of the same problems: bad register allocation leading to unnecessary moves and spills, failure to take advantage of flags already set by arithmetic and logic operations, and awkward code layout leading to unnecessary jumps and branches. Obviously, this isn't because compiler writers are stupid or ignorant. Instead, it's just a really difficult problem to optimize for one target in a way that doesn't hurt performance on another. Still, I think there's a lot of room for improvement, if only because comparing the output from GCC, CLang, and ICC often shows that one compiler does a much better job than the others[1]. But even once you know (definitively) that the code could be faster, it's often really tough to convince a given compiler to generate the code that you want. And coming up with a single construct that works well for multiple compilers and remains readable is often impossible. I'd love it if there was a good way to "lock down" the output for a given function, without needing to depend on the whims of individual compilers. Other than distributing only pre-compiled binaries, there doesn't seem to be a great way to do this. Looking at Dan's example code, he has a solution that's twice as fast as the non-inline-ASM alternatives. But it's still depending on the compiler to do register allocation, and adding a global variable somewhere else in the program might well cause the whole thing to blow up, spilling to stack and triggering the popcnt false dependency that he's working hard to avoid. What's the best way to do this --- to keep optimized code optimized across the mix of compilers in the current ecosystem? [1] I'm Linux based, but I probably should start adding MSVC to the mix. I'm sure there are cases where it does something better than the others. Is there a good way to do this? Perhaps an equivalent to http://gcc.godbolt.org/ for MSVC?
与其手写汇编或 intrinsic,不如让语言能更精确地向编译器表达意图
更理想的情况是根本不需要 intrinsic。你只需说明你想要什么以及所有约束:两个整数、符号性、输入范围、输入概率分布、想对它们做什么——然后让编译器去搞定。现在编译器经常生成次优代码,因为它必须考虑那些在这里其实无关紧要的边界情况。而且 C 的运算符定义不完善(带符号整数的'+'都不完全有定义),你甚至无法在不触碰未定义行为的情况下做简单的溢出检查。如果程序员能更好地表达意图,编译器就能选出最合适的 CPU 指令。所以程序员不该直接选指令(那牺牲可移植性),而应该有比 C 现在允许的更好的表达方式。
Aardwolf查看英文原文
Even more ideally you wouldn't need intrinsics. You'd just say what you want, with all constraits. E.g. I have two integers, this is their signedness, this is the range of expected inputs, this is the probability of the inputs, and this is what I want with them, nothing more... now you compiler figure it out. Now the compiler too often generates suboptimal code because it has to take edge cases that it doesn't know don't matter here into account. And then you have only those limited not actually well specified operators of C (something as simple as "+" is not fully specified on signed integers), so you can tell even less well what you actually want to the compiler... You can't even do a simple overflow check without bordering on undefined behaviour that allows compilers to do whatever they want. If you could tell better what you want, the compiler could better choose the perfect CPU instructions for it. So imho, a programmer shouldn't choose the CPU instruction as that doesn't allow portability, but the programmer should have the ability to specify things better than C now allows :)
历次讨论
同类还值得读
开源应用架构
The Architecture of Open Source Applications
《开源软件架构》(AOSA)是一本在线免费书,邀请知名开源项目的作者亲自剖析其内部设计,涵盖 SQLAlchemy、LLVM、Audacity、Apache、Bash 等系统。
编程与软件工程上首页 9 次 · 2011–2024aosabook.org编辑精选编程语言简史:简短、不完整且多半错误
A Brief, Incomplete, and Mostly Wrong History of Programming Languages
一篇以戏谑、故意不准确的方式回顾编程语言史的博客文章,用夸张轶事和讽刺定义串起 Lisp、C、C++、Java、JavaScript、PHP 等语言。
编程与软件工程上首页 8 次 · 2009–2021james-iry.blogspot.comSQLite 是如何测试的
How SQLite is tested
SQLite 官方文档页,系统介绍 SQLite 的测试体系:多种测试方法(TCL 测试、模糊测试、回归测试、静态分析等)和海量测试用例,展示了这个小型开源项目如何靠极致测试获得…
编程与软件工程上首页 8 次 · 2009–2025sqlite.org编辑精选高级编程语言(2009)
Advanced programming languages (2009)
Matt Might 的经典文章,按用途推荐“高级编程语言”:如果追求就业就选主流语言,如果追求表达力、学术研究或创业,则推荐 Haskell、OCaml、Scala 等函数式语…
编程与软件工程上首页 8 次 · 2009–2022matt.might.net我的 Forth 与栈机器经历
Yosefk - My history with Forth & stack machines
作者以亲身经历回顾了使用 Forth 语言和栈式机器的历程:既赞叹其简洁优雅、代码量小、可自举的优点,也坦率指出栈操作在实际编程中的痛苦,以及 Forth 社区文化中的问题。
编程与软件工程上首页 8 次 · 2010–2024yosefk.com编辑精选