Следующий код приводит к очень разному времени для g ++ и clang ++ при использовании uniform_real_distribution.

#include <iostream>
#include <sstream>
#include <fstream>

#include <chrono>
#include <random>


std::mt19937::result_type seed = 0;
std::mt19937 gen(seed);
// std::uniform_int_distribution<size_t> distr(0, 1);
std::uniform_real_distribution<double> distr(0.0,1.0);

int main()
{
    auto t_start = std::chrono::steady_clock::now();
    for (auto i = 1; i <= 1000000; ++i)
    {
        distr(gen);
    }
    auto t_end = std::chrono::steady_clock::now();
    std::cout << "elapsed time: " << std::chrono::duration_cast<std::chrono::nanoseconds>(t_end - t_start).count()  << " ns\n" << std::endl;

    return 0;
}

Скомпилировано с помощью следующих команд:

clang++ -std=c++17 -O3 -flto -march=native -mllvm -inline-threshold=10000000 rng.cpp -o rng
g++ -std=c++17 -O3 -march=native rng.cpp -o rng

Это приводит к следующим временам:

clang:  272929774 ns

gcc:    12054635 ns

При использовании прокомментированного распределения время:

clang:  48155862 ns

gcc:    50226810 ns

Я нашел здесь довольно старый вопрос, который решает ту же проблему, однако ни одно из предложенных решений не сработало в моем случае.

Падение производительности Clang при генерации случайных чисел в C ++

Кто-нибудь знает, что здесь происходит?

2
Satas 8 Окт 2019 в 12:17

1 ответ

Лучший ответ

Взгляните на godbolt

Компилятор gcc снес distr(gen); !!!

.L27:
        dec     esi
        je      .L25

Это цикл for, который ничего не делает!

Компилятор на clang оказался недостаточно умным:

.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        mov     edi, offset gen
        call    double std::generate_canonical<double, 53ul, std::mersenne_twister_engine<unsigned long, 32ul, 624ul, 397ul, 31ul, 2567483615ul, 11ul, 4294967295ul, 7ul, 2636928640ul, 15ul, 4022730752ul, 18ul, 1812433253ul> >(std::mersenne_twister_engine<unsigned long, 32ul, 624ul, 397ul, 31ul, 2567483615ul, 11ul, 4294967295ul, 7ul, 2636928640ul, 15ul, 4022730752ul, 18ul, 1812433253ul>&)
        dec     ebx
        jne     .LBB0_1

И действительно был вызван generate_canonical.

По сути, вы должны использовать результат distr(gen);, чтобы что-то с ним сделать, что повлияет на результат кода, иначе компилятор может удалить этот код.


Самый простой способ исправить я distr(gen);

Теперь, когда вы посмотрите на сборку, вы увидите, что clang вызывает функцию std::generate_canonical<double, 53ul, std::mersenne_twister_engine< .... >>, а gcc только что поместил соответствующий код в строку.

Скорее всего, это различие вызвано разной организацией стандартной библиотеки. Clang использовал версию, встроенную в стандартную библиотеку, а в шаблоне gcc из файла заголовка для генерации кода в только что созданной сборке. Когда компилятор достигает внешнего кода из библиотеки, он не может сказать, что именно он делает, поэтому он не может оптимизировать этот код (поскольку некоторые побочные эффекты могут быть скрыты в библиотеке).

2
Marek R 8 Окт 2019 в 11:47