模板化无分支int max / min函数

时间:2009-02-05 03:34:13

标签: c++ performance templates bit-manipulation

我正在尝试编写一个无分支函数来返回两个整数的MAX或MIN而不求助于if(或?:)。使用the usual technique我可以轻松地为给定的字大小做到这一点:

inline int32 imax( int32 a, int32 b )
{
    // signed for arithmetic shift
    int32 mask = a - b;
    // mask < 0 means MSB is 1.
    return a + ( ( b - a ) & ( mask >> 31 ) );
}

现在,假设arguendo我真的在那种必要的有序处理器上编写那种应用程序,我的问题是是否有办法使用C ++模板将此概括为全部int的大小。

&gt;&gt; 31 步骤当然适用于int32s,虽然我可以复制int8,int16和int64函数的重载,但似乎我应该使用而是模板功能。但是如何在中获取模板参数的大小?

有没有比这更好的方法呢?我可以强制对面具T进行签名吗?如果T是无符号的,则掩码移位步骤将不起作用(因为它将是逻辑移位而不是算术移位)。

template< typename T > 
inline T imax( T a, T b )
{
    // how can I force this T to be signed?
    T mask = a - b;
    // I hope the compiler turns the math below into an immediate constant!
    mask = mask >> ( (sizeof(T) * 8) - 1 );
    return a + ( ( b - a ) & mask );
}

并且,完成上述操作后,我可以阻止它被用于除整数类型之外的任何东西(例如,没有浮点数或类)吗?

6 个答案:

答案 0 :(得分:9)

通常看起来不错,但是为了100%可移植性,请用CHAR_BIT(或numeric_limits :: max())替换8,因为不能保证字符是8位。

任何好的编译器都足够聪明,可以在编译时合并所有的数学常量。

您可以使用类型特征库强制对其进行签名。这通常看起来像(假设您的numeric_traits库名为numeric_traits):

typename numeric_traits<T>::signed_type x;

手动滚动的numeric_traits标题的示例可能如下所示:http://rafb.net/p/Re7kq478.html(有足够的空间可以添加,但您明白了。)

或更好,使用boost:

typename boost::make_signed<T>::type x;
编辑:IIRC,签署右移不是必须算术。这是常见的,我使用的每个编译器都是如此。但是我相信标准会让编译器在签名类型上是右移也不算算。在我的标准草案副本中,写了以下内容:

  

E1的值&gt;&gt; E2是E1   右移E2位位置。如果E1   有一个无符号类型或E1有一个   签名类型和非负值,   结果的值是   E1的商的组成部分   除以提高的数量2   权力E2。 如果E1有签名类型   由此产生的负值   值是实现定义的

但就像我说的那样,它将适用于我见过的每个编译器:-p。

答案 1 :(得分:3)

这是无分支最大和最小的另一种方法。它有什么好处,它不会使用任何技巧,你不必知道任何类型的东西。

template <typename T> 
inline T imax (T a, T b)
{
    return (a > b) * a + (a <= b) * b;
}

template <typename T> 
inline T imin (T a, T b)
{
    return (a > b) * b + (a <= b) * a;
}

答案 2 :(得分:2)

您可能需要查看Boost.TypeTraits库。要检测类型是否已签名,您可以使用is_signed特征。您还可以查看enable_if/disable_if以删除某些类型的重载。

答案 3 :(得分:2)

tl; dr

要实现目标,最好只写以下代码:

template<typename T> T max(T a, T b) { return (a > b) ? a : b; }

长版

我既实现了max()的“天真”实现,又实现了您的无分支实现。两者都没有模板化,我改用int32只是为了使事情简单,而且据我所知,Visual Studio 2017不仅使朴素的实现变得无分支,而且产生的指令也更少。

这里是相关的Godbolt(请检查实现以确保我做对了)。请注意,我正在使用/ O2优化进行编译。

诚然,我的汇编功能并不十分出色,因此NaiveMax()的指令少了5条,并且没有明显的分支(并且内联,我确实不确定发生了什么),我想运行一个测试用例明确显示天真的实现是否更快。

所以我建立了一个测试。这是我运行的代码。具有“默认” Release编译器选项的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; }

天真的方法还有其他缺点:

  1. 它适用于无符号类型。
  2. 它甚至适用于浮动类型。
  3. 它精确地表达了您的意图,而无需注释描述位纠缠行为的代码。
  4. 这是一种众所周知的可识别模式,因此大多数编译器都将确切地知道如何对其进行优化,从而使其更易于移植。 (这是我的直觉,只有编译器的个人经验为后盾,这让我感到非常惊讶。我愿意承认我在这里错了。)

答案 4 :(得分:0)

我不知道该位掩码技巧起作用的 exact 条件是什么,但是您可以做 like

#include<type_traits>

template<typename T, typename = std::enable_if_t<std::is_integral<T>{}> > 
inline T imax( T a, T b )
{
   ...
}

其他有用的候选人包括std::is_[un]signedstd::is_fundamental等。https://en.cppreference.com/w/cpp/types

答案 5 :(得分:0)

除了 tloch14 的回答“tl;dr”之外,还可以使用数组的索引。这避免了“branchless min/max”的笨拙的bitshuffling;它也可以推广到所有类型。

template<typename T> constexpr T OtherFastMax(const T &a, const T &b)
{
    const T (&p)[2] = {a, b};
    return p[a>b];
}