2014-05-02 11 views
5

Napisałem następujące kody w R i C++, które wykonują ten sam algorytm:Dlaczego mój kod C++ jest o wiele wolniejszy od R?

a) Aby zasymulować zmienną losową X 500 razy. (X ma wartość 0.9 z prob 0.5 i 1.1 z prob 0.5)

b) Pomnożyć te 500 symulowanych wartości, aby uzyskać wartość. Zapisać tę wartość w pojemniku

c) Powtórz 10000000 czasy takie, że pojemnik ma 10000000 wartości

R:

ptm <- proc.time() 
steps <- 500 
MCsize <- 10000000 
a <- rbinom(MCsize,steps,0.5) 
b <- rep(500,times=MCsize) - a 
result <- rep(1.1,times=MCsize)^a*rep(0.9,times=MCsize)^b 
proc.time()-ptm 

C++

#include <numeric> 
#include <vector> 
#include <iostream> 
#include <random> 
#include <thread> 
#include <mutex> 
#include <cmath> 
#include <algorithm> 
#include <chrono> 

const size_t MCsize = 10000000; 
std::mutex mutex1; 
std::mutex mutex2; 
unsigned seed_; 
std::vector<double> cache; 

void generatereturns(size_t steps, int RUNS){ 
    mutex2.lock(); 
    // setting seed 
    try{  
     std::mt19937 tmpgenerator(seed_); 
     seed_ = tmpgenerator(); 
     std::cout << "SEED : " << seed_ << std::endl; 
    }catch(int exception){ 
     mutex2.unlock(); 
    } 
    mutex2.unlock(); 

    // Creating generator 
    std::binomial_distribution<int> distribution(steps,0.5); 
    std::mt19937 generator(seed_); 

    for(int i = 0; i!= RUNS; ++i){ 
     double power; 
     double returns; 
     power = distribution(generator); 
     returns = pow(0.9,power) * pow(1.1,(double)steps - power); 
     std::lock_guard<std::mutex> guard(mutex1); 
     cache.push_back(returns); 
    } 
}  


int main(){ 
    std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); 
    size_t steps = 500; 
    seed_ = 777;  

    unsigned concurentThreadsSupported = std::max(std::thread::hardware_concurrency(),(unsigned)1); 
    int remainder = MCsize % concurentThreadsSupported; 

    std::vector<std::thread> threads; 
    // starting sub-thread simulations 
    if(concurentThreadsSupported != 1){ 
     for(int i = 0 ; i != concurentThreadsSupported - 1; ++i){ 
      if(remainder != 0){ 
       threads.push_back(std::thread(generatereturns,steps,MCsize/ concurentThreadsSupported + 1)); 
       remainder--; 
      }else{ 
       threads.push_back(std::thread(generatereturns,steps,MCsize/ concurentThreadsSupported)); 
      } 
     } 
    } 

    //starting main thread simulation 
    if(remainder != 0){ 
     generatereturns(steps, MCsize/concurentThreadsSupported + 1); 
     remainder--; 
    }else{ 
     generatereturns(steps, MCsize/concurentThreadsSupported); 
    } 

    for (auto& th : threads) th.join(); 

    std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now() ; 
    typedef std::chrono::duration<int,std::milli> millisecs_t ; 
    millisecs_t duration(std::chrono::duration_cast<millisecs_t>(end-start)) ; 
    std::cout << "Time elapsed : " << duration.count() << " milliseconds.\n" ; 

    return 0; 
} 

nie mogę zrozumieć, dlaczego mój Kod R jest znacznie szybszy niż mój kod C++ (3.29s vs 12s), mimo że użyłem czterech wątków w kodzie C++? Czy ktoś może mnie oświecić, proszę? Jak mogę poprawić mój kod C++, aby działał szybciej?

EDYTOWANIE:

Dzięki za radę! Zarezerwowałem pojemność dla moich wektorów i zmniejszyłem ilość blokowania w moim kodzie. Kluczowa zmiana w generatereturns() jest:

std::vector<double> cache(MCsize); 
std::vector<double>::iterator currit = cache.begin(); 
//..... 

// Creating generator 
std::binomial_distribution<int> distribution(steps,0.5); 
std::mt19937 generator(seed_); 
std::vector<double> tmpvec(RUNS); 
for(int i = 0; i!= RUNS; ++i){ 
    double power; 
    double returns; 
    power = distribution(generator); 
    returns = pow(0.9,power) * pow(1.1,(double)steps - power); 
    tmpvec[i] = returns; 
} 
std::lock_guard<std::mutex> guard(mutex1); 
std::move(tmpvec.begin(),tmpvec.end(),currit); 
currit += RUNS; 

Zamiast blokowania za każdym razem, stworzyłem tymczasową wektor, a następnie wykorzystywane std :: ruch przesunięcie elementów w tej tempvec do pamięci podręcznej. Czas, który upłynął, został skrócony do 1,9 sekundy.

+0

kompilator? OS?ustawienia kompilatora? maszyna? Przepraszamy, nie ma dla nas informacji! – Klaus

+0

Wygląda na to, że robisz dużo nadmiernego blokowania w swoim kodzie. Czy próbowałeś najpierw wersji bez wątków? W przeciwnym razie powinieneś spróbować zapisać wynik w oddzielnych wektorach <>, które nie muszą być blokowane, łącząc dane wyjściowe na samym końcu. Spróbuj także rezerwować pojemność dla swoich wektorów. –

+0

Używam Visual Studio Express z Windows 8. Mój procesor to intel i5 z quadcore. – user22119

Odpowiedz

2

Po pierwsze, uruchamiasz go w trybie zwolnienia? Przełączenie z trybu debugowania na zwolnienie skróciło czas pracy z ~ 15s do ~ 4.5s na moim laptopie (Windows 7, iM).

Również zmniejszenie liczby wątków do 2 zamiast 4 w moim przypadku (mam tylko 2 rdzenie, ale z hyperthreading) dodatkowo skróciło czas działania do ~ 2.4s.

Zmiana mocy zmiennej na int (tak jak sugerował także jimifiki) również nieznacznie przyspieszyła, skracając czas do ~ 2.3s.

1

Prawdopodobnie nie pomoże ci tak bardzo, ale zacznij od użycia pow (double, int), gdy twój wykładnik jest int.

int power; 
returns = pow(0.9,power) * pow(1.1,(int)steps - power); 

Czy widzisz jakieś ulepszenie?

1

Naprawdę podobało mi się twoje pytanie i próbowałem kodu w domu. Próbowałem zmienić generator liczb losowych, moja implementacja std :: binomial_distribution wymaga przeciętnie około 9.6 wywołań generatora().

Wiem, że pytanie dotyczy raczej porównania wydajności R z C++, ale ponieważ pytasz "Jak poprawić mój kod C++, aby działał szybciej?" Nalegam z optymalizacją mocy. Możesz łatwo ominąć połowę połączenia, wstępnie wykonując 0,9^kroków lub 1,1^kroków przed pętlą for. To sprawia, że ​​Twój kod uruchomić nieco szybciej:

double power1 = pow(0.9,steps); 
double ratio = 1.1/0.9; 
for(int i = 0; i!= RUNS; ++i){ 
    ... 
    returns = myF1 * pow(myF2, (double)power); 

Analogicznie można poprawić kod R:

... 
ratio <-1.1/0.9 
pow1 = 0.9^steps 
result <- rep(ratio,times=MCsize)^rep(pow1,times=MCsize) 
... 
Powiązane problemy