C++ 中使用位运算符或 if 语句更快吗?

问题描述 投票:0回答:4

我在某处读到,尽可能使用按位运算符而不是 if 语句会更快。我正在研究一个图像处理项目,并且有多种方法对像素进行数学计算。例如,当我添加一个像素时,我会检查并确保总和不会超过最大值。我改成这样了...

    Pixel16 operator+(Pixel16 p) const noexcept
    {
        uint_fast32_t r = red + p.red;
        uint_fast32_t g = green + p.green;
        uint_fast32_t b = blue + p.blue;
        return Pixel16(r | -(r > 0xffff), g | -(g > 0xffff), b | -(b > 0xffff));
    }

你们认为这比写这样的语句更快吗

if(r > 0xffff)
 r = 0xffff;

仅供参考,红色、绿色和蓝色是 uint16_t 类型的成员变量

c++ performance bitwise-operators
4个回答
2
投票

给出这段代码:

#include <algorithm>
#include <cstdint>

struct Pixel16 {
    uint16_t red;
    uint16_t blue;
    uint16_t green;

    Pixel16(uint16_t red, uint16_t green, uint16_t blue);

};

Pixel16 v1(Pixel16 const & p, Pixel16 const & s) {
    uint_fast32_t r = p.red   + s.red;
    uint_fast32_t g = p.green + s.green;
    uint_fast32_t b = p.blue  + s.blue;
    return Pixel16(r | -(r > 0xffff), g | -(g > 0xffff), b | -(b > 0xffff));
}

Pixel16 v2(Pixel16 const & p, Pixel16 const & s) {
    uint_fast32_t r = p.red   + s.red;
    uint_fast32_t g = p.green + s.green;
    uint_fast32_t b = p.blue  + s.blue;

    r = std::min(r, (uint_fast32_t) 0xFFFF);
    g = std::min(g, (uint_fast32_t) 0xFFFF);
    b = std::min(b, (uint_fast32_t) 0xFFFF);

    return Pixel16(r, g, b);
}

我的编译器给出什么结果?

OS X 上的

Clang 将为

v1
v2
生成功能相同的代码。唯一的区别是对
min()
的调用顺序和
v1
中的等效工作以不同的顺序发生。

在这两种情况下,都没有分支。

总结:

写出最容易理解的代码。使用函数和语言特性以可读的方式表达您的代码。你的按位代码比

min()
函数可读性更好还是更差?


1
投票

在几乎所有情况下,“更快”将在很大程度上取决于目标系统和所使用的编译器,以及运算符如何重载。不过,在这种情况下,我严重怀疑会有那么大的差异,因为您仍在使用比较运算符,这应该是比简单的

if
分支更昂贵的操作。

但是上面列出的代码让我很困扰。取反比较操作的结果(boolean操作,除非您已经覆盖了运算符)对于按位来说不是安全的,或者像您正在做的那样。而且它非常难以理解,这意味着以后维护起来会非常困难。不过,

if
版本解释了发生了什么。


1
投票

了解,您必须测量(分析代码)。

关于为什么按位运算可能更快的理论是,分支预测失败可能会严重影响性能。通过使用按位运算而不是控制流开关,您可以完全避免条件分支,因此您永远不会有分支预测丢失。

根据具体情况,计算可能比分支对缓存稍微友好一些。

但是除非您进行测量,否则您不会知道实际效果。影响因素太多,无法简单分析代码。


0
投票

这就是三十年前所谓的微优化,因为它可以节省微秒。今天我将其称为纳米优化:-)

除非您有理由相信速度正在影响任何人,否则请做更具可读性的事情。

如果速度很重要,例如因为您需要每秒处理 60 张 10 兆像素图像,那么您可以尝试各种方法并测量执行时间。确切的速度取决于你的编译器、处理器,有时纯粹取决于好运气或坏运气。显然,您使用与生产代码中相同的编译器优化设置。

哪些代码产生最快的结果在不同的处理器上可能会有所不同。因此,您将所有变体保留在源代码中(包括最可读的变体),并使用 #ifdef 选择您测量过的已知最快的变体。假设您的代码应该在三个当前设备和五个未来设备上运行。您在两台当前设备上进行了测量。然后,您确保在这两个设备上运行最快的测量代码。在其他设备上,这是一个判断调用。如果 A 在处理器 X 上比 B 快 40%,但在处理器 Y 上慢 5%,那么您可能会在未测量速度的所有处理器上使用 A。并且您将测量结果添加为注释。

现在所有这些都是大量的工作,所以只有当节省的时间值得你付出这些努力时,你才会这样做。

© www.soinside.com 2019 - 2024. All rights reserved.