在 C++ 中使用位运算符还是使用 if 语句更快?
Is it faster to use bit operators or if statements in C++?
我在某处读到,在可能的情况下使用按位运算符而不是 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
类型的成员变量
在几乎所有情况下,"faster" 将在很大程度上取决于目标系统和使用的编译器,以及运算符的重载方式。不过,在这种情况下,我严重怀疑会有那么大的不同,因为您仍在使用比较运算符,这应该是比简单的 if
分支更昂贵的操作。
不过,上面列出的代码让我很困扰。否定比较运算的结果(布尔值 运算,除非您已经覆盖运算符)对于按位或像您正在做的那样是不安全的。此外,它很难理解,这意味着以后有人很难维护。不过,if
版本解释了发生了什么。
要知道,您必须测量(剖析代码)。
为什么按位运算 可能 更快的理论是分支预测失败可能会严重影响性能。通过使用按位运算而不是控制流开关,您可以完全避免条件分支,因此您永远不会错过那里的分支预测。
根据具体情况,计算而不是分支可能对缓存更友好。
但除非您进行测量,否则您不会知道实际效果。影响因素太多,无法简单分析代码。
鉴于此代码:
#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);
}
我的编译器给出了什么结果?
Clang on OS X 将为 v1
和 v2
生成功能相同的代码。唯一的区别是对 min()
的调用顺序和 v1
中的等效工作以不同的顺序发生。
在这两种情况下,都没有分支。
总结:
写出最容易理解的代码。使用函数和语言特性以可读的方式表达您的代码。您的按位代码比 min()
函数更易读还是更差?
我在某处读到,在可能的情况下使用按位运算符而不是 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
类型的成员变量在几乎所有情况下,"faster" 将在很大程度上取决于目标系统和使用的编译器,以及运算符的重载方式。不过,在这种情况下,我严重怀疑会有那么大的不同,因为您仍在使用比较运算符,这应该是比简单的 if
分支更昂贵的操作。
不过,上面列出的代码让我很困扰。否定比较运算的结果(布尔值 运算,除非您已经覆盖运算符)对于按位或像您正在做的那样是不安全的。此外,它很难理解,这意味着以后有人很难维护。不过,if
版本解释了发生了什么。
要知道,您必须测量(剖析代码)。
为什么按位运算 可能 更快的理论是分支预测失败可能会严重影响性能。通过使用按位运算而不是控制流开关,您可以完全避免条件分支,因此您永远不会错过那里的分支预测。
根据具体情况,计算而不是分支可能对缓存更友好。
但除非您进行测量,否则您不会知道实际效果。影响因素太多,无法简单分析代码。
鉴于此代码:
#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);
}
我的编译器给出了什么结果?
Clang on OS X 将为 v1
和 v2
生成功能相同的代码。唯一的区别是对 min()
的调用顺序和 v1
中的等效工作以不同的顺序发生。
在这两种情况下,都没有分支。
总结:
写出最容易理解的代码。使用函数和语言特性以可读的方式表达您的代码。您的按位代码比 min()
函数更易读还是更差?