特定C++随机数生成的Clang性能下降

23

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

罪魁祸首并不只是mt生成器,因为使用uniform_int_distribution时速度很快。而且也不是uniform_real_distribution的普遍缺陷,因为它与其他生成器(如default_random_engine)一样快。只有这个特定的组合速度异常缓慢。

虽然我对内在操作不是很熟悉,但梅森旋转算法几乎是严格定义的,所以我猜差异不可能是由于实现造成的。程序测量如下,以下是我在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++ 进行编译。有什么想法吗?

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


3
我只能在float_mt的情况下复现这个问题,而不能在float_mt_64中。我在Fedora 20 64位上使用clang3.4来运行您的代码。 - Baum mit Augen
-O3对于两者都是必须的,在帖子底部列出。 - Basti
14
@Basti: 你知道两者是否都使用libstdc++,还是Clang使用libc++吗?标准库实现的更改肯定会产生巨大影响。作为另一个比较点,你可能想尝试提高Clang中的内联级别,并观察发生的变化。例如,使用-mllvm -inline-treshold=10000,因为我记得Clang默认的内联阈值比gcc低,这可能会影响进一步的优化(特别是常量传播)。 - Matthieu M.
1
我不知道这些库。但是内联解决了它!哇,谢谢。 - Basti
如果你使用-flto启用了链接时优化,即使没有内联更改,clang的时间也会回到预期水平。性能调优并不是一件简单的事情。 - jepio
显示剩余4条评论
1个回答

6

正如评论中所述,问题是由于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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接