为什么编译器不将浮点*2优化为指数增量?

use*_*108 46 c c++ optimization performance compiler-optimization

我经常注意到gcc将乘法转换为可执行文件中的移位.当乘以a int和a 时,可能会发生类似的事情float.例如,2 * f可能只是将指数递增f1,从而节省了一些周期.编译器,也许是一个人请求他们这样做(例如通过-ffast-math),一般来说,这样做吗?

编译器通常是否足够聪明,或者我是否需要使用scalb*()ldexp()/frexp()函数系列自己完成此操作?

Mys*_*ial 80

例如,2*f,可能只是将f的指数递增1,从而节省了一些周期.

这根本不是真的.

首先你有太多的角落情况,如零,无穷大,楠和非正规.然后你就遇到了性能问题.

误解是增加指数并不比进行乘法更快.

如果查看硬件指令,则无法直接增加指数.所以你需要做的是:

  1. 按位转换为整数.
  2. 增加指数.
  3. 按位转换回浮点.

在整数和浮点执行单元之间移动数据通常存在中到大延迟.所以最后,这种"优化"变得比简单的浮点乘法更糟糕.

因此,编译器不执行此"优化"的原因是因为它不是更快.

  • 内置与否,你真的看过他们生成的指令吗?你有没有对它们进行基准测试?无论编译器做什么都无关紧要,它仍然必须尊重ISA. (8认同)
  • 我假设x86,因为你没有指定.但对于其他架构而言,它并没有太大的不同. (4认同)
  • @ user1095108它可能*具有*硬件,但它不能通过指令访问(单独).把它想象成一个类的私有功能. (4认同)
  • 是的,转换是必要的.在C/C++中,你可以通过union使用type-punning来实现.由于现代处理器的设计方式,这种"转换"并不便宜.(特别是使用单独的整数和浮点单位.) (2认同)
  • @AkiSuihkonen是的,因此我省略了从这个3步骤过程中的掩蔽/移位步骤.昂贵的部分是按位转换本身(类型 - 惩罚). (2认同)
  • @Mysticial:我认为如果写“从浮点堆栈(到RAM)到通用累加器的复制”(并返回)而不是“按位转换”,那将会更加清楚。在几乎所有情况下,按位转换都是免费的,因为这意味着“对通用累加器进行处理,就好像它具有其他类型一样”,但是在这种情况下不是这样。 (2认同)

R..*_*R.. 22

在现代CPU上,乘法通常具有每周期一个吞吐量和低延迟.如果该值已经存在于浮点寄存器中,则无法通过对其进行处理以对表示进行整数运算来击败它.如果它在内存中开始,如果你假设当前值和正确的结果都不是零,非正规,纳米或无穷大,那么执行类似的东西可能会更快

addl $0x100000, 4(%eax)   # x86 asm example
Run Code Online (Sandbox Code Playgroud)

乘以2; 唯一一次我能看到这是有益的,如果你在一系列浮点数据上操作,这些浮点数据与零和无穷大有限,并且以2的幂为单位缩放是你将要执行的唯一操作(所以您没有任何现有理由将数据加载到浮点寄存器中).

  • 当它可能实际上更快时给出一个可能的情况+1(即使我怀疑它会值得做的麻烦) (2认同)
  • 如果您在低端/嵌入式系统上使用多通道,高采样率浮点音频,并且大部分只是通过它,这种优化可能很有用. (2认同)

Eri*_*hil 18

常见的浮点格式(尤其是IEEE 754)不会将指数存储为简单整数,并将其视为整数将不会产生正确的结果.

在32位浮点或64位双精度中,指数字段分别为8或11位.指数代码1到254(浮点数)或1到2046(双精度数)的行为类似于整数:如果您将这些值中的一个添加到其中一个,并且结果是这些值之一,则表示的值会加倍.但是,在这些情况下添加一个会失败:

  • 初始值为0或次正规.在这种情况下,指数字段从零开始,向其添加一个将数字添加2 -126(浮点数)或2 -1022(双精度数); 它的数量不会翻倍.
  • 初始值超过2 127(浮动)或2 1023(双倍).在这种情况下,指数字段从254或2046开始,向其添加一个将数字更改为NaN; 它的数量不会翻倍.
  • 初始值为无穷大或NaN.在这种情况下,指数字段从255或2047开始,并且向其添加一个将其更改为零(并且可能溢出到符号位).结果为零或次正规,但应分别为无穷大或NaN.

(以上是积极的迹象.情况是对称的,有负面的迹象.)

正如其他人所指出的,某些处理器没有快速操作浮点值位的工具.即使在这样做的情况下,指数字段也不会与其他位隔离,因此通常无法在上面的最后一种情况下将符号位添加到符号位中.

虽然某些应用程序可以容忍快捷方式,例如忽略次正规或NaN甚至无穷大,但应用程序很少会忽略零.因为向指数添加一个无法正确处理零,所以它不可用.


Aki*_*nen 9

这不是编译器或编译器编写者不聪明.它更像是遵守标准并产生所有必要的"副作用",如Infs,Nans和denormals.

此外,它可能不会产生其他不需要的副作用,例如读取内存.但我确实认识到在某些情况下会更快.

  • @ user1095108但是编译器怎么会知道你不关心? (4认同)