何时汇编比C更快?

已知的了解汇编器的缘由之一是,有时能够用它来编写比用高级语言(尤为是C)编写更高性能的代码。 可是,我也听到过不少次声明,尽管这并不是彻底错误,但实际上可将汇编程序用于生成更多性能代码的状况极为罕见,而且须要汇编方面的专业知识和经验。 算法

这个问题甚至都没有涉及到汇编程序指令将是特定于机器且不可移植的,或者汇编程序的任何其余方面。 固然,除了汇编语言以外,还有不少了解汇编语言的充分理由,但这是一个特定的问题,须要征集示例和数据,而不是对汇编语言和高级语言的扩展论述。 编程

谁能提供一些特定的例子说明使用现代编译器进行汇编比编写良好的C代码要快得多,而且您能够提供带有分析依据的主张吗? 我对这些案例的存在颇有信心,可是我真的想确切地知道这些案例有多深奥,由于这彷佛有些争议。 缓存


#1楼

根据个人经验,有几个例子: 架构

  • 访问没法从C访问的指令。例如,许多体系结构(如x86-64,IA-64,DEC Alpha和64位MIPS或PowerPC)支持64位乘64位乘法,产生128位结果。 GCC最近添加了扩展名,以提供对此类说明的访问,可是在须要该程序集以前。 当实施RSA之类的东西时,访问此指令可能对64位CPU产生巨大的影响-有时性能会提升4倍。 分布式

  • 访问特定于CPU的标志。 咬住我不少的是进位标志; 在进行多精度加法运算时,若是您没法访问CPU进位,则必须比较结果以查看其是否溢出,这每条肢体须要3-5条指令; 更糟糕的是,就数据访问而言,这是串行的,这会破坏现代超标量处理器的性能。 当连续处理成千上万个这样的整数时,可以使用addc是一个巨大的胜利(进位位上的争用也存在超标量问题,但现代CPU处理起来很不错)。 函数

  • SIMD。 即便是自动向量化的编译器也只能处理相对简单的状况,所以,若是要得到良好的SIMD性能,一般经常须要直接编写代码。 固然,您可使用内部函数而不是汇编程序,可是一旦您进入内部函数级别,则基本上不管如何都在编写汇编程序,只是将编译器用做寄存器分配器和(名义上)指令调度程序。 (我倾向于将内在函数用于SIMD只是由于编译器能够为我生成函数序言,而不是为我生成函数序言,所以我能够在Linux,OS X和Windows上使用相同的代码而没必要处理函数调用约定之类的ABI问题,但其余比SSE内在函数确实不是很好-Altivec的内在函数彷佛更好,尽管我对它们没有太多经验。 做为一个事例,(当今)矢量化编译器没法弄清楚,请阅读有关位片化AESSIMD纠错的信息 -能够想象一个编译器能够分析算法并生成这样的代码,但在我看来,这就像一个聪明的编译器距现有(至少)至少30年。 工具

另外一方面,多核计算机和分布式系统已将许多最大的性能优点转移到了另外一个方向上-将内部循环以汇编形式编写可额外提升20%的速度,或者经过在多个内核上运行它们来实现300%的加速,或在10000%的速度下达到10000%在一组机器上运行它们。 固然,使用ML或Scala这样的高级语言比使用C或asm进行高级优化(诸如期货,备忘录等之类的东西)一般要容易得多,而且一般能够带来更大的性能优点。 所以,一如既往,须要进行权衡。 性能


#2楼

简单的答案... 知道汇编的 (又有他的参考,而且利用每个小的处理器高速缓存和管道功能等)能够保证比任何编译器产生更快的代码。 优化

可是,这些天的差别在典型应用中可有可无。 编码


#3楼

从汇编编码器的角度来看,C常常比您想像的要多作没必要要的事情,由于C标准如此说。

例如,整数提高。 若是要在C中移动char变量,一般会但愿代码实际上只是这样作,即一次移位。

可是,这些标准强制编译器在移位以前对int进行符号扩展,而后将结果截断为char,这可能会使代码复杂化,具体取决于目标处理器的体系结构。


#4楼

Longpoke,只有一个限制:时间。 若是您没有足够的资源来优化代码的每一个更改,并花时间分配寄存器,优化少许溢出,而没有的话,则编译器将每次都获胜。 您对代码进行修改,从新编译和测量。 若有必要,请重复。

另外,您能够在高级方面作不少事情。 一样,检查生成的程序集可能会使IMPRESSION感受到代码已被废弃,但实际上它的运行速度要比您认为的更快。 例:

int y = data [i]; //在这里作一些事情.. call_function(y,...);

编译器将读取数据,将其压入堆栈(溢出),而后从堆栈中读取并做为参数传递。 听起来很糟糕? 它实际上多是很是有效的延迟补偿,而且能够加快运行时间。

//优化的版本call_function(data [i],...); //毕竟没有那么优化。

优化版本的想法是,咱们减小了套准压力并避免了溢出。 但实际上,“糟糕”版本的速度更快!

查看汇编代码,仅查看说明并得出结论:更多的说明,较慢的说明将是错误的判断。

这里要注意的事情是:许多组装专家认为他们了解不少,但了解不多。 规则也从体系结构更改成下一个。 例如,没有银弹x86代码,它老是最快的。 这些天最好遵循经验法则:

  • 记忆很慢
  • 快取
  • 尝试更好地使用缓存
  • 你多久想念一次? 您有延迟补偿策略吗?
  • 您能够为一个缓存未命中执行10-100条ALU / FPU / SSE指令
  • 应用程序体系结构很重要。
  • ..可是当问题不在体系结构中时它没有帮助

一样,过度相信编译器会神奇地将思想欠佳的C / C ++代码转换为“理论上最佳”的代码,这是一厢情愿的想法。 若是您在此低级关注“性能”,则必须了解所使用的编译器和工具链。

对于初学者来讲,C / C ++中的编译器一般不太擅长从新排序子表达式,由于这些函数具备反作用。 函数式语言不会受到这种警告的困扰,但并不能很好地适应当前的生态系统。 有一些编译器选项容许宽松的精度规则,这些规则容许由编译器/连接器/代码生成器更改操做顺序。

这个话题有点死胡同。 对于大多数状况而言,这是可有可无的,其他的,他们不管如何都知道本身在作什么。

归结为:“了解本身在作什么”,这与知道本身在作什么有些不一样。


#5楼

我已经阅读了全部答案(超过30个),却找不到简单的缘由:若是您已经阅读并练习了《 英特尔®64和IA-32架构优化参考手册》 , 那么汇编程序的运行速度会比C快。 更慢的是写这样慢的汇编的人没有看过“优化手册” 。

在Intel 80286的美好时光中,每条指令均以固定的CPU周期数执行,可是自1995年发布的Pentium Pro起,Intel处理器就利用了复杂流水线:乱序执行和寄存器重命名,成为了超标量。 在此以前,在1993年生产的Pentium上有U和V管线:双管线能够在不依赖时在一个时钟周期执行两条简单指令的状况; 但这与奔腾Pro中出现的乱序执行和寄存器重命名没有什么可比的,现在几乎保持不变。

用几句话来解释,最快的代码是指指令不依赖于先前的结果,例如,您应始终清除整个寄存器(经过movzx)或使用add rax, 1inc rax来消除对标志先前状态的依赖等。 。

若是时间容许,您能够阅读更多关于乱序执行和注册重命名的信息,Internet上有不少可用信息。

还有其余重要问题,例如分支预测,加载和存储单元的数量,执行微操做的门的数量等,可是要考虑的最重要的事情是无序执行。

大多数人根本不了解乱序执行,所以他们像80286同样编写汇编程序,指望他们的指令将花费固定的时间来执行,而与上下文无关; 而C编译器知道乱序执行并正确生成代码。 这就是为何这种不了解的人的代码速度较慢的缘由,可是若是您意识到这一点,您的代码就会更快。

相关文章
相关标签/搜索