2014-04-23 1 views
22

Mit zufälligen C++ 11-Modul, traf ich eine ungerade Leistungsabfall bei Verwendung std::mt19937 (32 und 64bit-Versionen) in Kombination mit einem uniform_real_distribution (float oder double, spielt keine Rolle) . Im Vergleich zu einer g ++ Kompilierung ist es mehr als eine Größenordnung langsamer!Clang Performance-Drop für spezifische C++ Zufallszahlengenerierung

Der Schuldige ist nicht nur der mt-Generator, wie es mit einem uniform_int_distribution schnell ist. Und es ist kein allgemeiner Fehler in der uniform_real_distribution, da das schnell mit anderen Generatoren wie default_random_engine ist. Nur diese spezielle Kombination ist merkwürdig langsam.

Ich bin nicht sehr vertraut mit den intrinsics, aber der Mersenne Twister-Algorithmus ist mehr oder weniger streng definiert, so ein Unterschied in der Umsetzung könnte nicht für diesen Unterschied verantwortlich sein, denke ich? messen Programm folgt, aber hier sind meine Ergebnisse für Klirren 3.4 und gcc 4.8.1 auf einem 64-Bit-Linux-Rechner:

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 

Programm diese zu erzeugen und selbst ausprobieren:

#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; 
} 

kompilieren über clang++ -O3 -std=c++11 random.cpp bzw. g ++. Irgendwelche Ideen?

edit: Endlich hatte Matthieu M. eine tolle Idee: Der Schuldige ist Inlining, oder eher ein Mangel daran. Durch Erhöhen des Claming-Inlining-Limits wurde die Leistungseinbuße beseitigt. Das hat tatsächlich eine Anzahl von Performance-Kuriositäten gelöst, denen ich begegnet bin. Danke, ich habe etwas Neues gelernt.

+0

Vielleicht möchten Sie die Dinge ein wenig Profil (z. B. mit Callgrind) und vergleichen generierten Assembler ... – PlasmaHH

+3

Ich kann nur reproduzieren dies für den Fall 'float_mt', nicht für' float_mt_64'. Ich habe Ihren Code mit clang3.4 auf Fedora 20 64-Bit verwendet. –

+0

Ich wollte sagen, dass du einen Fehlerbericht gepostet hast, aber ich habe gesehen, dass du es bereits getan hast, http://llvm.org/bugs/show_bug.cgi?id=19542 – pyCthon

Antwort

4

Wie bereits in den Kommentaren erwähnt, wird das Problem durch die Tatsache verursacht, dass gcc Inlines aggressiver als clang. Wenn wir Klirren inline sehr aggressiv machen, verschwindet der Effekt:

Kompilieren Sie Ihren Code mit g++ -O3 ergibt

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 

während clang++ -O3 -mllvm -inline-threshold=10000 Ausbeuten

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 

Offenbar Klirren jetzt out-inlines gcc in der int_mt Fälle, aber alle anderen Laufzeiten sind jetzt in der gleichen Größenordnung. Ich habe gcc 4.8.3 und clang 3.4 auf Fedora 20 64 bit benutzt.