【问题标题】:Specialization of STL TemplatesSTL 模板的专业化
【发布时间】:2018-12-28 21:03:41
【问题描述】:

我正在尝试使用 Mersenne Twister 编写使用随机数的高性能代码。大约需要~5ns 来生成随机的unsigned long long。这用于生成double,但是,这些需要~40ns 在分发中生成。

查看STL代码doubles,由分发生成,是通过调用std::generate_canonical生成的,这涉及到std::ceilstd::log2的操作,我相信是这些成本很高。

这些操作是不必要的,因为它们用于计算调用任何 RNG 实现所需的位数。由于这是在编译时间之前就知道的,我已经编写了自己的实现,不会进行这些调用,生成double 的时间是~15ns

是否可以专门化模板化的 STL 函数?如果是这样,这是如何实现的,到目前为止我的尝试导致原始功能仍在使用。我想专门研究这个 STL 函数,因为我仍然想使用 <random> 中的分布。

这是在 Visual C++ 中,不过一旦代码开发完成,它将在 Linux 上运行并使用 GCC 或 ICC。如果在 Linux 上生成双精度的方法不同,(而且更快),这个问题是无关紧要的。

编辑 1:

我相信所有需要对 std::generate_canonical 进行双重调用的分布,此函数在 [0,1) 范围内创建一个双重,并且通过迭代添加对 RNG operator() 的调用来创建正确的精度。 log2ceil 用于计算迭代次数。

MSVCstd::generate_canonical

// FUNCTION TEMPLATE generate_canonical
template<class _Real,
size_t _Bits,
class _Gen>
_Real generate_canonical(_Gen& _Gx)
{   // build a floating-point value from random sequence
_RNG_REQUIRE_REALTYPE(generate_canonical, _Real);

const size_t _Digits = static_cast<size_t>(numeric_limits<_Real>::digits);
const size_t _Minbits = _Digits < _Bits ? _Digits : _Bits;

const _Real _Gxmin = static_cast<_Real>((_Gx.min)());
const _Real _Gxmax = static_cast<_Real>((_Gx.max)());
const _Real _Rx = (_Gxmax - _Gxmin) + static_cast<_Real>(1);

const int _Ceil = static_cast<int>(_STD ceil(
    static_cast<_Real>(_Minbits) / _STD log2(_Rx)));
const int _Kx = _Ceil < 1 ? 1 : _Ceil;

_Real _Ans = static_cast<_Real>(0);
_Real _Factor = static_cast<_Real>(1);

for (int _Idx = 0; _Idx < _Kx; ++_Idx)
    {   // add in another set of bits
    _Ans += (static_cast<_Real>(_Gx()) - _Gxmin) * _Factor;
    _Factor *= _Rx;
    }

return (_Ans / _Factor);
}

我的简化版

template<size_t _Bits>
double generate_canonical(std::mt19937_64& _Gx)
{   // build a floating-point value from random sequence
    const double _Gxmin = static_cast<double>((_Gx.min)());
    const double _Gxmax = static_cast<double>((_Gx.max)());
    const double _Rx = (_Gxmax - _Gxmin) + static_cast<double>(1);

    double _Ans = (static_cast<double>(_Gx()) - _Gxmin);

    return (_Ans / _Rx);
}

这个函数写在namespace std {}

编辑 2:

我找到了解决方案,请在下面查看我的答案。

【问题讨论】:

  • 请提供最低代码以显示您的问题
  • 为什么不把你的代码包装在一个新的distribution object中?
  • 您可以专门化一个标准模板,但您只能针对您自己的自定义类型这样做,而不是内置的。
  • 在 linux 中你不使用 Visual C++ 编译器,检查他的问题是否仍然存在,例如GCC 或 Clang
  • @JorgeBellón:为什么不呢? Visual C++ 现在以 Linux 为目标。

标签: c++ c++11 templates visual-c++ stl


【解决方案1】:

抱歉,不允许专门化标准库函数;这样做会导致未定义的行为。

但是,您可以使用其他发行版; C++ 在生成器和发行版之间具有良好定义的接口。

哦,只是为了消除初学者错误的可能性(因为您没有显示代码):您不要为每个数字创建一个新的分布。

【讨论】:

  • 一般情况下,您可以将特化添加到std,但前提是它们至少依赖于您自己的一种类型。
  • @Quentin: 是的,但这是一般规则的例外情况,这里的例外情况并不适用,因为这里的类型是double
  • 是的,这只是一个挑剔。虽然我很确定你一般可以,因为一些标准类型特征明确声明专门化它们是 UB。
  • 你能限定一下吗抱歉,不允许专门化标准库函数;这样做会导致未定义的行为。 在您的答案中。不对,当它只被禁止用于内置类型时,它读作一揽子禁令。
  • @Quentin 总而言之,double 周围的薄包装可以很容易地做到这一点。所以标准禁止专门针对内置插件真的不是问题还是我错了?
【解决方案2】:

通过创建一个设置了所有参数的模板函数,并将函数声明为inline,可以创建std::generate_canonical的用户定义版本。

用户定义std::generate_canonical

namespace std {
    template<>
    inline double generate_canonical<double, static_cast<size_t>(-1), std::mt19937>(std::mt19937& _Gx)
    {   // build a floating-point value from random sequence
        const double _Gxmin = static_cast<double>((_Gx.min)());
        const double _Rx = (static_cast<double>((_Gx.max)()) - _Gxmin) + static_cast<double>(1);

        double _Ans = (static_cast<double>(_Gx()) - _Gxmin);
        _Ans += (static_cast<double>(_Gx()) - _Gxmin) *_Rx;

        return (_Ans / _Rx * _Rx);
    }

    template<>
    inline double generate_canonical<double, static_cast<size_t>(-1), std::mt19937_64>(std::mt19937_64& _Gx)
    {   // build a floating-point value from random sequence
        const double _Gxmin = static_cast<double>((_Gx.min)());
        const double _Rx = (static_cast<double>((_Gx.max)()) - _Gxmin) + static_cast<double>(1);

        return ((static_cast<double>(_Gx()) - _Gxmin) / _Rx);
    }
}

第二个参数static_cast&lt;size_t&gt;(-1) 应该修改为特定库使用的任何值,VC++ 就是这种情况,但 GCC 可能不同。这意味着它不可移植。

此函数已为std::mt19337std::mt19937_64 定义,并且似乎可以正确用于STL 分发。

结果:

double using std::generate_canonical

Generating 400000000 doubles using standard MT took: 17625 milliseconds
This equivalent to: 44.0636 nanoseconds per value

Generating 400000000 doubles using 64bit MT took: 11958 milliseconds
This equivalent to: 29.8967 nanoseconds per value


double using new generate_canonical

Generating 400000000 doubles using standard MT took: 4843 milliseconds
This equivalent to: 12.1097 nanoseconds per value

Generating 400000000 doubles using 64bit MT took: 2645 milliseconds
This equivalent to: 6.61362 nanoseconds per value

【讨论】:

  • 不要使用static_cast&lt;size_t&gt;(-1)。它仅适用于 2 的补码表示法 - 它不可移植。
  • 它是直接取自 MSVC STL 代码的初始化,这就是它被使用的原因。我相信 GCC 的情况有所不同,但我并没有深入研究问题的这方面
  • @bartop:它是完全可移植的。这是一个演员,具有明确定义的行为。无符号类型被定义为模 2^N。您对签名行为的位模式感到困惑,但这里不涉及位模式。这是由数学定义的。
猜你喜欢
  • 2020-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-28
相关资源
最近更新 更多