tl;博士
为了实现你的目标,你最好只写这个:
template<typename T> T max(T a, T b) { return (a > b) ? a : b; }
长版
我实现了“幼稚”的实现max()
以及您的无分支实现。它们都没有模板化,我使用 int32 只是为了简单起见,据我所知,Visual Studio 2017 不仅使幼稚的实现无分支,而且产生的指令也更少。
这是相关的Godbolt(请检查实现以确保我做对了)。请注意,我正在使用 /O2 优化进行编译。
诚然,我的组装符并不是那么好,所以虽然NaiveMax()
少了 5 条指令并且没有明显的分支(并且内联我真的不确定发生了什么),但我想运行一个测试用例来明确显示是否天真的实现是更快与否。
所以我建立了一个测试。这是我运行的代码。带有“默认”版本编译器选项的 Visual Studio 2017 (15.8.7)。
#include <iostream>
#include <chrono>
using int32 = long;
using uint32 = unsigned long;
constexpr int32 NaiveMax(int32 a, int32 b)
{
return (a > b) ? a : b;
}
constexpr int32 FastMax(int32 a, int32 b)
{
int32 mask = a - b;
mask = mask >> ((sizeof(int32) * 8) - 1);
return a + ((b - a) & mask);
}
int main()
{
int32 resInts[1000] = {};
int32 lotsOfInts[1'000];
for (uint32 i = 0; i < 1000; i++)
{
lotsOfInts[i] = rand();
}
auto naiveTime = [&]() -> auto
{
auto start = std::chrono::high_resolution_clock::now();
for (uint32 i = 1; i < 1'000'000; i++)
{
const auto index = i % 1000;
const auto lastIndex = (i - 1) % 1000;
resInts[lastIndex] = NaiveMax(lotsOfInts[lastIndex], lotsOfInts[index]);
}
auto finish = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count();
}();
auto fastTime = [&]() -> auto
{
auto start = std::chrono::high_resolution_clock::now();
for (uint32 i = 1; i < 1'000'000; i++)
{
const auto index = i % 1000;
const auto lastIndex = (i - 1) % 1000;
resInts[lastIndex] = FastMax(lotsOfInts[lastIndex], lotsOfInts[index]);
}
auto finish = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start).count();
}();
std::cout << "Naive Time: " << naiveTime << std::endl;
std::cout << "Fast Time: " << fastTime << std::endl;
getchar();
return 0;
}
这是我在我的机器上得到的输出:
Naive Time: 2330174
Fast Time: 2492246
我已经运行了几次,得到了类似的结果。为了安全起见,我还更改了进行测试的顺序,以防万一它是核心速度加快的结果,从而扭曲了结果。在所有情况下,我都得到与上述类似的结果。
当然,根据您的编译器或平台,这些数字可能都不同。值得自己测试。
答案
max()
简而言之,编写无分支模板函数的最佳方法似乎是保持简单:
template<typename T> T max(T a, T b) { return (a > b) ? a : b; }
天真的方法还有其他好处:
- 它适用于无符号类型。
- 它甚至适用于浮动类型。
- 它准确地表达了您的意图,而不是需要注释您的代码来描述位旋转正在做什么。
- 这是一种众所周知且可识别的模式,因此大多数编译器将确切地知道如何优化它,使其更具可移植性。(这是我的直觉,只有编译器的个人经验支持,这让我很惊讶。我愿意承认我在这里错了。)