【问题标题】:C/C++: Multiply, or bitshift then divide? [duplicate]C/C++:乘法,还是移位然后除法? [复制]
【发布时间】:2014-07-21 23:33:38
【问题描述】:

在可能的情况下,我想知道用移位后跟整数除法替换单个乘法是否更快。假设我有一个 int k,我想将它乘以 2.25。

什么更快?

int k = 5;
k *= 2.25;
std::cout << k << std::endl;

int k = 5;
k = (k<<1) + (k/4);
std::cout << k << std::endl;

输出

11
11

两者都给出相同的结果,你可以检查this full example

【问题讨论】:

  • k 是整数还是浮点数?
  • “什么更快”:有一种简单的方法可以找出...
  • @Jongware: 加上不可能;)
  • 另外,如果您愿意使用 bitshift,为什么不两种方式都使用? k = (k&lt;&lt;1) + (k&gt;&gt;2);?
  • 由于这高度依赖于架构,我会让编译器优化代码。我很确定编译器比现代架构上的大多数人更聪明,因为指令集、流水线、指令单元等有许多限制。

标签: c++ c multiplication bit-shift


【解决方案1】:

这在很大程度上取决于 CPU 架构:包括乘法在内的浮点运算在许多 CPU 上变得相当便宜。但是必要的 float->int 转换可能会咬你一口:例如,在 POWER-CPU 上,由于在将值从浮点单元移动到整数单元时生成的管道刷新,常规乘法将一直爬行。

在某些 CPU 上(包括我的 AMD CPU),这个版本实际上是最快的:

k *= 9;
k >>= 2;

因为这些 CPU 可以在一个周期内执行 64 位整数乘法。其他 CPU 使用我的版本肯定比使用你的 bitshift 版本慢,因为它们的整数乘法没有经过高度优化。大多数 CPU 的乘法运算已经不像以前那么糟糕了,但是一次乘法运算仍然需要超过四个周期。

因此,如果您知道您的程序将在哪个 CPU 上运行,请测量哪个 CPU 最快。如果您不知道,您的 bitshift 版本在任何架构上都不会表现不佳(与常规版本和我的不同),这使它成为一个非常安全的选择。

【讨论】:

    【解决方案2】:

    第一次尝试

    我定义了函数regularmultiply()bitwisemultiply()如下:

    int regularmultiply(int j)
    {
        return j * 2.25;
    }
    
    int bitwisemultiply(int k)
    {
        return (k << 1) + (k >> 2);
    }
    

    在使用 Instruments 进行分析时(在 2009 Macbook OS X 10.9.2 上的 XCode 中),bitwisemultiply 的执行速度似乎比 regularmultiply 快 2 倍。

    汇编代码输出似乎证实了这一点,bitwisemultiply 将大部分时间用于寄存器改组和函数返回,而 regularmultiply 将大部分时间用于乘法。

    正则乘法

    按位乘法

    但我的试炼时间太短了。

    第二次尝试

    接下来,我尝试使用 1000 万次乘法来执行这两个函数,这次将循环放入函数中,这样所有函数的进入和离开都不会混淆数字。而这一次,结果是每个方法都花费了大约 52 毫秒的时间。因此,至少对于相对较大但不是大量的计算,这两个函数需要大约相同的时间。这让我很吃惊,所以我决定计算更长的时间和更大的数字。

    第三次尝试

    这一次,我只将 1 亿乘以 5 亿乘以 2.25,但 bitwisemultiply 实际上比 regularmultiply 慢一点。

    最后的尝试

    最后,我切换了这两个函数的顺序,只是为了看看 Instruments 中不断增长的 CPU 图表是否可能会减慢第二个函数的速度。不过,regularmultiply 的表现还是稍好一些:

    这是最终程序的样子:

    #include <stdio.h>
    
    int main(void)
    {
        void regularmultiplyloop(int j);
        void bitwisemultiplyloop(int k);
    
        int i, j, k;
    
        j = k = 4;
        bitwisemultiplyloop(k);
        regularmultiplyloop(j);
    
        return 0;
    }
    
    void regularmultiplyloop(int j)
    {
        for(int m = 0; m < 10; m++)
        {
            for(int i = 100000000; i < 500000000; i++)
            {
                j = i;
                j *= 2.25;
            }
            printf("j: %d\n", j);
        }
    }
    
    void bitwisemultiplyloop(int k)
    {
        for(int m = 0; m < 10; m++)
        {
            for(int i = 100000000; i < 500000000; i++)
            {
                k = i;
                k = (k << 1) + (k >> 2);
            }
            printf("k: %d\n", k);
        }
    }
    

    结论

    那么我们能对这一切说些什么呢?我们可以肯定地说,优化编译器比大多数人都好。此外,当有大量计算时,这些优化会更加突出,这是您真正想要优化的唯一时间。因此,除非您在汇编中编写优化代码,否则将乘法更改为位移可能不会有太大帮助。

    考虑应用程序的效率总是好的,但微效率的收益通常不足以保证降低代码的可读性。

    【讨论】:

    • 您可能只是在测试编译器识别仅保留循环的最终结果的能力,从而完全放弃循环。您还需要检查这些情况的程序集输出。
    • @MarkRansom 如果您查看代码,它不会重复乘以相同的值。它依次执行 1 亿到 5 亿次,然后重复。
    • @MarkRansom 测量的性能在当前循环的预期范围内。如果编译器对它们进行了优化,运行时间会短得多。
    • 这两个图像中的汇编代码看起来像你用-O0 编译的。因此,按位乘法在操作更多中间值时会进行更多的堆栈访问。这使得它变慢了,即使这些堆栈访问是完全没有必要的。如果使用-O2-Os 编译,图片应该会发生巨大变化。
    • 不,默认不优化,和-O0一样。
    【解决方案3】:

    确实,这取决于多种因素。所以我刚刚通过运行和测量时间来检查它。所以我们感兴趣的字符串只需要几条非常快的 CPU 指令,所以我将它包装到循环中 - 将一个代码的执行时间乘以一个大数字,我得到 k *= 2.25; 大约在 1.5比k = (k&lt;&lt;1) + (k/4); 慢几倍。 这是我要比较的两个代码:

    程序1:

    #include <iostream>
    using namespace std;
    
    int main() {
    
    int k = 5;
    for (unsigned long i = 0; i <= 0x2fffffff;i++)
     k = (k<<1) + (k/4);
    cout << k << endl;
    
    return 0;
    }
    

    程序 2:

    #include <iostream>
    using namespace std;
    
    int main() {
    
    int k = 5;
    for (unsigned long i = 0; i <= 0x2fffffff;i++)
     k *= 2.25;
    cout << k << endl;
    
    return 0;
    }
    

    Prog1 需要 8 秒,Prog2 需要 14 秒。因此,通过使用您的架构和编译器运行此测试,您可以获得对您的特定环境正确的结果。

    【讨论】:

      【解决方案4】:

      这在很大程度上取决于您使用的硬件。在现代硬件上,浮点乘法可能比整数乘法运行得更快,因此您可能想要更改整个算法并开始使用双精度数而不是整数。如果您正在为现代硬件编写代码,并且您有很多操作,例如乘以 2.25,我建议您使用双精度数而不是整数,如果没有其他方法阻止您这样做的话。

      并且是数据驱动的 - 衡量性能,因为它受编译器、硬件和您实现算法的方式的影响。

      【讨论】:

      • “在现代硬件上,浮点乘法可能比整数乘法运行得更快”...真的吗?你有这方面的参考吗?我从来没有意识到这一点......
      • 当然。刚刚在我的 Mac Book 上进行了测量:gist.github.com/avshabanov/b4960e95c8b68575ad27 注意:为了公平比较,我比较了双倍乘法与长长乘法+右移来模拟具有整数原始类型的非整数乘法。
      • @Mehrdad 如果您考虑自动矢量化,这当然是可能的。在极端情况下,Haswell 处理器可以通过双发出 AVX 乘法执行 8 DP-MUL/周期。但是 Haswell 每个周期只能做一个 64 位整数乘法。 64 位整数乘法没有 SIMD。
      • @Mehrdad 是的,他是对的,浮点乘法可以比整数乘法更快。可能与浮点乘法对于获得高 machoflop 至关重要的事实有关......请原谅,gigaflop 数字。
      • @Mehrdad 64 位整数乘法必须计算 128 位结果。双精度乘法只需要计算 53 位有效数。指数部分对结果的影响是微不足道的。
      猜你喜欢
      • 2011-02-02
      • 1970-01-01
      • 1970-01-01
      • 2018-03-27
      • 1970-01-01
      • 2023-03-25
      • 1970-01-01
      • 2014-07-19
      • 2015-10-17
      相关资源
      最近更新 更多