【问题标题】:Integer division, or float multiplication?整数除法还是浮点乘法?
【发布时间】:2023-03-25 13:36:01
【问题描述】:

如果必须计算给定 int 值的一小部分,例如:

int j = 78;
int i = 5* j / 4;

这比做的快吗:

int i = 1.25*j; // ?

如果是,是否有一个转换因子可以用来决定使用哪一个,例如在一个float 乘法的同时可以完成多少个int 除法?

编辑:我认为 cmets 明确表示浮点数学会变慢,但问题是,慢了多少?如果我需要将每个 float 乘法替换为 N int 除法,那么 N 将不再值得这样做吗?

【问题讨论】:

  • 你对每一个都进行了基准测试吗?
  • 这些数字中有多少是动态的?
  • @KerrekSB 指的是编译器为您优化所有这些。因此,为什么基准测试很重要。
  • 这似乎是过早的优化,还要注意1.25字面量实际上是double,而不是float
  • 您展示的两个备选方案可能会产生不同的结果。先根据想要的结果进行选择,再根据性能进行选择。

标签: c++ optimization floating-point division multiplication


【解决方案1】:

你说过所有的价值观都是动态的,这会有所不同。对于特定值5 * j / 4,整数运算将非常快,因为几乎最坏的情况是编译器将它们优化为两个班次和一个加法,加上一些混乱以应对j 的可能性是负数。如果 CPU 可以做得更好(单周期整数乘法或其他),那么编译器通常会知道它。编译器优化此类事情的能力的限制基本上出现在您为广泛的 CPU 系列进行编译(例如,生成最小公分母 ARM 代码)时,编译器并不真正了解硬件,因此不能总是做出好的选择。

我想如果ab 被固定了一段时间(但在编译时不知道),那么有可能计算k = double(a) / b 一次,然后计算int(k * x) 对于x 的许多不同值,可能对于许多不同的x 值计算a * x / b 更快。我不会指望它。

如果所有值每次都不同,那么计算1.25 的浮点除法,然后是浮点乘法,似乎不太可能比整数乘法和整数除法更快。但你永远不知道,测试一下。

在现代处理器上不可能给出简单的相对时序,这在很大程度上取决于周围的代码。代码中的主要成本通常不是“实际”操作:它是“不可见”的东西,例如指令管道在依赖项上停滞,或将寄存器溢出到堆栈,或函数调用开销。执行这项工作的函数是否可以内联可能很容易比函数实际执行它的方式产生更大的差异。就性能的明确声明而言,您基本上可以测试真实代码或闭嘴。但是很有可能,如果您的值以整数开头,对它们执行整数操作将比转换为 double 并执行类似数量的 double 操作更快。

【讨论】:

    【解决方案2】:

    不可能断章取义地回答这个问题。此外,5*j/4 通常不会产生与(int) (1.25*j) 相同的结果,这是由于整数和浮点运算的属性,包括舍入和溢出。

    如果您的程序主要执行整数运算,则将 j 转换为浮点数、乘以 1.25 以及转换回整数可能是免费的,因为它使用了其他情况下不涉及的浮点单位。

    另外,在某些处理器上,操作系统可能会将浮点状态标记为无效,以便进程第一次使用它时,出现异常,操作系统会保存浮点寄存器(其中包含来自另一个进程的值),恢复或初始化您的进程的寄存器,并从异常中返回。相对于正常的指令执行而言,这将花费大量时间。

    答案还取决于程序正在执行的特定处理器模型的特征,以及操作系统、编译器如何将源代码转换为程序集,甚至可能还取决于系统上的其他进程正在做什么。

    此外,5*j/4(int) (1.25*j) 之间的性能差异通常太小而无法在程序中注意到,除非它或类似的操作重复很多次。 (如果是的话,向量化代码可能会带来巨大的好处,即使用许多现代处理器的单指令多数据 [SIMD] 功能来一次执行多个操作。)

    【讨论】:

      【解决方案3】:

      在您的情况下,5*j/4 将比 1.25*j 快得多,因为除以 2 的幂可以通过右移轻松操作,5*j 可以通过许多架构上的单个指令完成,例如 @ x86 上的 987654328@,或 ARM 上的 ADD 移位。大多数其他人最多需要2条指令,例如j + (j >> 2),但这样它仍然可能比浮点乘法更快。此外,通过int i = 1.25*j,您需要从intdouble 的2 次转换,以及2 次跨域数据移动,这通常非常昂贵

      在其他情况下,如果分数不能用二进制浮点表示(如 3*j/10),那么使用 int 乘法/除法会更正确(因为 0.3 在浮点数中不完全是 0.3 -point),而且很可能更快(因为编译器可以optimize out division by a constant 将其转换为乘法


      如果ij 是浮点类型,则乘以另一个浮点值可能会更快。因为在 float 和 int 域之间移动值需要时间,而 int 和 float 之间的转换也需要时间,正如我上面所说的

      一个重要的区别是如果 j 太大,5*j/4 会溢出,但1.25*j 不会

      也就是说,对于“哪个更快”和“多快”的问题没有通用答案,因为它取决于特定的架构和特定的上下文。您必须对您的系统进行测量并做出决定。但是如果一个表达式对很多值重复执行,那么是时候转移到 SIMD 了

      另见

      【讨论】:

        猜你喜欢
        • 2011-05-06
        • 2012-08-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-06-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多