【问题标题】:Clang performance drop for specific C++ random number generation特定 C++ 随机数生成的 Clang 性能下降
【发布时间】:2014-06-08 01:36:36
【问题描述】:

使用 C++11 的随机模块时,我在使用 std::mt19937(32 位和 64 位版本)与 uniform_real_distribution(浮点型或双精度型,没关系)结合使用时遇到了奇怪的性能下降。与 g++ 编译相比,它慢了一个数量级以上!

罪魁祸首不仅仅是 mt 生成器,因为它的速度很快,uniform_int_distribution。这不是uniform_real_distribution 中的一般缺陷,因为这对于default_random_engine 等其他生成器来说很快。只是那个特定的组合非常慢。

我对内在函数不是很熟悉,但 Mersenne Twister 算法或多或少是严格定义的,所以我猜在实现上的差异不能解释这种差异?测量程序如下,但这是我在 64 位 linux 机器上的 clang 3.4 和 gcc 4.8.1 的结果:

gcc 4.8.1
runtime_int_default: 185.6
runtime_int_mt: 179.198
runtime_int_mt_64: 175.195
runtime_float_default: 45.375
runtime_float_mt: 58.144
runtime_float_mt_64: 94.188

clang 3.4
runtime_int_default: 215.096
runtime_int_mt: 201.064
runtime_int_mt_64: 199.836
runtime_float_default: 55.143
runtime_float_mt: 744.072  <--- this and
runtime_float_mt_64: 783.293 <- this is slow

生成这个并自己尝试的程序:

#include <iostream>
#include <vector>
#include <chrono>
#include <random>

template< typename T_rng, typename T_dist>
double time_rngs(T_rng& rng, T_dist& dist, int n){
    std::vector< typename T_dist::result_type > vec(n, 0);
    auto t1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < n; ++i)
        vec[i] = dist(rng);
    auto t2 = std::chrono::high_resolution_clock::now();
    auto runtime = std::chrono::duration_cast<std::chrono::microseconds>(t2-t1).count()/1000.0;
    auto sum = vec[0]; //access to avoid compiler skipping
    return runtime;
}

int main(){
    const int n = 10000000;
    unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
    std::default_random_engine rng_default(seed);
    std::mt19937 rng_mt (seed);
    std::mt19937_64 rng_mt_64 (seed);
    std::uniform_int_distribution<int> dist_int(0,1000);
    std::uniform_real_distribution<float> dist_float(0.0, 1.0);

    // print max values
    std::cout << "rng_default_random.max(): " << rng_default.max() << std::endl;
    std::cout << "rng_mt.max(): " << rng_mt.max() << std::endl;
    std::cout << "rng_mt_64.max(): " << rng_mt_64.max() << std::endl << std::endl;

    std::cout << "runtime_int_default: " << time_rngs(rng_default, dist_int, n) << std::endl;
    std::cout << "runtime_int_mt: " << time_rngs(rng_mt_64, dist_int, n) << std::endl;
    std::cout << "runtime_int_mt_64: " << time_rngs(rng_mt_64, dist_int, n) << std::endl;
    std::cout << "runtime_float_default: " << time_rngs(rng_default, dist_float, n) << std::endl;
    std::cout << "runtime_float_mt: " << time_rngs(rng_mt, dist_float, n) << std::endl;
    std::cout << "runtime_float_mt_64: " << time_rngs(rng_mt_64, dist_float, n) << std::endl;
}

分别通过clang++ -O3 -std=c++11 random.cpp 或 g++ 编译。有什么想法吗?

edit:最后,Matthieu M. 有了一个好主意:罪魁祸首是内联,或者说是缺乏内联。增加 clang 内联限制消除了性能损失。这实际上解决了我遇到的一些性能异常。谢谢,我学到了一些新东西。

【问题讨论】:

  • 也许您想稍微分析一下(例如使用 callgrind)并比较生成的汇编程序...
  • 我只能为float_mt 的情况重现这个,而不是float_mt_64。我在 Fedora 20 64 位上使用了您的代码和 clang3.4。
  • 本想说发布错误报告,但我看到你已经这样做了,llvm.org/bugs/show_bug.cgi?id=19542
  • @Basti:你知道两者都使用 libstdc++ 还是 Clang 使用 libc++ ?当然,标准库实现的改变会产生巨大的影响。作为另一个比较点,您可能想尝试提高 Clang 的内联级别,看看会发生什么-mllvm -inline-treshold=10000(例如),因为我似乎记得 Clang 默认的内联阈值低于 gcc,这可能会影响进一步优化(特别是持续传播)。
  • 我不知道这些库。但是那个内联修复了它!哇,谢谢

标签: c++ random clang mersenne-twister


【解决方案1】:

正如 cmets 中已经说明的那样,问题是由于 gcc 内联比 clang 更具攻击性造成的。如果我们非常激进地使 clang 内联,效果就会消失:

使用g++ -O3 编译代码会产生

runtime_int_default: 3000.32
runtime_int_mt: 3112.11
runtime_int_mt_64: 3069.48
runtime_float_default: 859.14
runtime_float_mt: 1027.05
runtime_float_mt_64: 1777.48

clang++ -O3 -mllvm -inline-threshold=10000 产生

runtime_int_default: 3623.89
runtime_int_mt: 751.484
runtime_int_mt_64: 751.132
runtime_float_default: 1072.53
runtime_float_mt: 968.967
runtime_float_mt_64: 1781.34

显然,clang 现在在 int_mt 情况下超出了内联 gcc,但所有其他运行时现在都处于同一数量级。我在 Fedora 20 64 位上使用了 gcc 4.8.3 和 clang 3.4。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-16
    • 1970-01-01
    • 2015-09-22
    • 2017-01-02
    • 2011-04-08
    • 1970-01-01
    • 2020-02-05
    相关资源
    最近更新 更多