完全理解现代 pc 架构是不是有可能获得比编译器更好的性能?

Posted

技术标签:

【中文标题】完全理解现代 pc 架构是不是有可能获得比编译器更好的性能?【英文标题】:Is it possible to get better performance than a compiler by fully comprehending modern pc architecture?完全理解现代 pc 架构是否有可能获得比编译器更好的性能? 【发布时间】:2020-11-08 21:48:38 【问题描述】:

我知道现在很多编译器都非常擅长优化代码。但是,如果一个人完全理解现代 pc 架构,是否有可能让代码比编译器更快? 比如,如果他用 100% 的汇编编写代码,专注于架构呢?如果它确实有所作为,是否值得?

【问题讨论】:

有时是可能的。但是,这非常困难。击败编译器的最好方法是改进程序,而不是通过优化汇编来追求性能上的微小提升。 是的,但今天了解很多不仅仅是处理器,处理器之外的东西对性能起着重要作用。整个系统的详细文档,包括在 x86 上的经验都不是现成的。 由于 pc (x86) 世界/历史的性质,使在您的机器上运行良好的代码在另一台机器上可能/将会变慢。对于 x86,您希望获得一个没有针对特定系统或系列调整的良好通用平均值。 不难找到可以出于各种原因改进编译器输出的地方。因此,获取编译器输出并使其“更好”并不需要太多工作。 是的,C++ code for testing the Collatz conjecture faster than hand-written assembly - why? 有一节介绍如何为那个小循环击败编译器。改进需要数小时/数天的人力(相对于编译器需要几秒钟),并进行基准测试以验证它是否是真正的改进,并且在实践中使用 asm 非常不方便,因此很少这样做。 【参考方案1】:

是的! 经验丰富的开发人员可以在特定任务上明显击败编译器(考虑到相对大量的时间)。

一个原因是开发人员可以比编译器获得更多关于给定任务的信息(开发人员可以试验算法,了解数据大小、可能的输入、程序的执行上下文) .另一个原因是编译器并不完美(它们使用启发式算法)并且经常无法进行高级代码转换。

但是,通常只向编译器提供提示、调整编译参数、插入内联汇编或内置调用就足够了,而不是在汇编中编写完整的程序。

一个很好的例子是使用低级处理器指令,例如non-temporal instructions 或SIMD instructions 以及bit-wise instructions。这些指令通常可以从具有足够提示的编译器中生成。对于register allocation,目标硬件专家有可能设计出更好的汇编代码(在这种情况下,编译器提示是不够的)。

【讨论】:

【参考方案2】:

如果其中一些要求是真实的,有时人类可以产生更好的代码:

    人类需要有关目标架构的特定知识。 人类知道编译器的所有技巧,例如(左移而不是乘法)。 此外,人类还需要了解很多关于汇编/处理器的知识,例如流水线停顿、缓存未命中…… 人类将需要大量时间来完成重要的程序。

比如,如果他用 100% 汇编编写代码,专注于架构会怎样?

这个程序在这个 CPU 上会非常快,但是你必须为每个 cpu 从头开始​​重写它。 (就像您使用更快的 shr 指令为处理器 1 编写的一样,但处理器 2 具有更快的 div 指令。) 此外,开发时间将显着延长(高达 20 倍)==>更高的成本

如果它确实有所作为,是否值得?

仅适用于一小部分应用程序,例如为微控制器编写代码,或者如果您真的需要纯粹的性能(数据的数据处理,这无法在 GPU 上完成)。

更多信息: When is assembly faster than C?

但是: 首先使用其他算法,例如使用 Coppersmith–Winograd 算法而不是用于矩阵乘法的朴素算法。只有在使用所有其他可能性时,才使用组装,否则您很快就会陷入维护噩梦。

【讨论】:

可维护性是一个关键点,IMO。编译器可以内联并在整个程序中进行持续传播,在进行小的更改后为小函数重新优化每个调用站点。为 asm 手动执行此操作将是一场噩梦,因此对于完全用 asm 编写的项目,您可能实际上已经调用了某个函数(或使用了宏)并且在某些情况下错过了优化。即编译器可以快速重做优化以创建手动无法维护的 asm。手写 asm 只值得考虑用于孤立的热循环或块,而不是通常整个项目。 这里的中间立场是使用内在函数的手动 SIMD 矢量化(例如 Intel 的 software.intel.com/sites/landingpage/IntrinsicsGuide)。您可以使用硬件原语操作来完成编译器不会从普通标量代码创建的事情。但是编译器仍然会填写数组索引计算等细节。【参考方案3】:

是的,人类在汇编程序中编码可以击败编译器。但总的来说,您最好将宝贵的时间花在更高层次的优化上。

为什么人类可以击败编译器?

因为编译器是由具有目标架构知识的人设计的。因此,在知识水平相同的情况下,人类可以生成至少与编译结果一样性能的汇编代码。

它可能会更好,因为人类开发人员可以针对给定任务进行优化,而编译器只能应用通用优化。

为什么这是个坏主意?

一切都与开发成本有关。

用汇编语言开发比用高级语言开发花费很多很多的时间,并且降低了可读性和可维护性。

在大多数情况下,您最好将相同的开发时间投入到高级优化中,例如更好的算法、局部优化,所有这些都基于对应用程序的全面分析以找到真正的瓶颈。

有了装配解决方案所需的预算,您甚至可以让两三个独立的、相互竞争的团队开发他们的高级解决方案,然后让他们将他们最好的想法组合成一个最终版本,并且仍有预算来进一步优化那个。

【讨论】:

在某些时候,“局部优化”可能会使用像 __builtin_popcountll_mm_shuffle_epi8 这样的内在函数。但通常它会停在那里,除非热点 非常 重要并且编译器对你的内在函数做得不好,而不是编译成你想要的 asm。 (这显然在 ARM SIMD 中仍然很常见,其中编译器的性能比 x86 或 PowerPC 的 SIMD 内在函数差得多)。在这一点上,值得考虑 asm for that one loop。当然,不是为了整个项目,现在没有人这样做,除了个人原因/乐趣。 (例如,FASM 是用汇编语言编写的。)

以上是关于完全理解现代 pc 架构是不是有可能获得比编译器更好的性能?的主要内容,如果未能解决你的问题,请参考以下文章

Data Mapper 是否比 Active Record 更现代的趋势?

架构师,别再扯淡了!

SE是啥编译器

使用类似于 Lenet5 的架构对“101 个对象类别”数据进行分类,获得比预期更高的准确度

课堂练习

Shiro笔记——简介 架构分析