41 votes

C++11 multithread lent

Je suis en train autour de la nouvelle C++11 fils, mais mon test simple a insondable de performances multicœurs. Comme exemple simple, ce programme ajoute quelques carrés de nombres aléatoires.

#include <iostream>
#include <thread>
#include <vector>
#include <cstdlib>
#include <chrono>
#include <cmath>

double add_single(int N) {
    double sum=0;
    for (int i = 0; i < N; ++i){
        sum+= sqrt(1.0*rand()/RAND_MAX);
    }
    return sum/N;
}

void add_multi(int N, double& result) {
    double sum=0;
    for (int i = 0; i < N; ++i){
        sum+= sqrt(1.0*rand()/RAND_MAX);
    }
    result = sum/N;
}

int main() {
    srand (time(NULL));
    int N = 1000000;

    // single-threaded
    auto t1 = std::chrono::high_resolution_clock::now();
    double result1 = add_single(N);
    auto t2 = std::chrono::high_resolution_clock::now();
    auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
    std::cout << "time single: " << time_elapsed << std::endl;

    // multi-threaded
    std::vector<std::thread> th;
    int nr_threads = 3;
    double partual_results[] = {0,0,0};
    t1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < nr_threads; ++i) 
        th.push_back(std::thread(add_multi, N/nr_threads, std::ref(partual_results[i]) ));
    for(auto &a : th)
        a.join();
    double result_multicore = 0;
    for(double result:partual_results)
        result_multicore += result;
    result_multicore /= nr_threads;
    t2 = std::chrono::high_resolution_clock::now();
    time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(t2-t1).count();
    std::cout << "time multi: " << time_elapsed << std::endl;

    return 0;
}

Compilé avec g++ -std=c++11 -pthread test.cpp' sur Linux et un 3core machine, un résultat typique est

time single: 33
time multi: 565

Donc le multi version filetée est plus d'un ordre de grandeur inférieur. J'ai utilisé des nombres aléatoires et un sqrt à prendre l'exemple moins trivial et sujettes à des optimisations du compilateur, donc je suis à court d'idées.

edit:

  1. Ce problème des échelles plus grand de N, de sorte que le problème n'est pas à court d'exécution
  2. Le temps pour créer les threads n'est pas le problème. Hors, il ne change pas le résultat de manière significative

Wow j'ai trouvé le problème. En effet, il était rand(). Je l'ai remplacé par un C++11 équivalent et maintenant le moteur d'exécution des échelles parfaitement. Merci à tous!

26voto

Croniak Points 366

Sur mon système, le comportement est le même, mais comme Maxim mentionné, rand n'est pas thread-safe. Quand je change de rand à rand_r, puis le multi threaded code est plus rapide que prévu.

void add_multi(int N, double& result) {
double sum=0;
unsigned int seed = time(NULL);
for (int i = 0; i < N; ++i){
    sum+= sqrt(1.0*rand_r(&seed)/RAND_MAX);
}
result = sum/N;
}

20voto

Nate Kohl Points 11240

Comme vous l'avez découvert, rand est le coupable ici.

Pour ceux qui sont curieux, il est possible que ce problème vient de votre mise en œuvre de l' rand à l'aide d'un mutex pour la sécurité des threads.

Par exemple, eglibc définit rand en termes de __random, ce qui est défini comme:

long int
__random ()
{
  int32_t retval;

  __libc_lock_lock (lock);

  (void) __random_r (&unsafe_state, &retval);

  __libc_lock_unlock (lock);

  return retval;
}

Ce type de verrouillage serait la force d'exécuter plusieurs threads en série, résultant en une baisse des performances.

8voto

Claudio Points 2533

Le temps nécessaire pour exécuter le programme est très petite (33msec). Cela signifie que la surcharge de créer et de gérer plusieurs threads peuvent être plus que le réel avantage. Essayez d'utiliser des programmes qui ont besoin de plus de temps pour l'exécution (par exemple, 10 sec).

3voto

Yakk Points 31636

Pour rendre cela plus rapidement, utiliser un pool de threads modèle.

Cela vous permettra de mettre en file d'attente des tâches dans d'autres threads sans la surcharge de la création d'un std::thread chaque fois que vous voulez utiliser plus d'un thread.

Ne comptez pas les frais généraux de la configuration de la file d'attente dans vos indicateurs de performance, juste le temps de mettre en file d'attente et d'en extraire les résultats.

Créer un ensemble de threads et une file d'attente de tâches (une structure contenant une std::function<void()>) pour les nourrir. Les fils d'attente dans la file d'attente pour les nouvelles tâches à faire, le faire, puis d'attendre de nouvelles tâches.

Les tâches sont tenues de communiquer leurs "fait-ness" retour vers le contexte de l'appel, par exemple via une std::future<>. Le code qui vous permet de mettre en file d'attente des fonctions dans la tâche de la file d'attente peut faire cet emballage pour vous, c'est à dire cette signature:

template<typename R=void>
std::future<R> enqueue( std::function<R()> f ) {
  std::packaged_task<R()> task(f);
  std::future<R> retval = task.get_future();
  this->add_to_queue( std::move( task ) ); // if we had move semantics, could be easier
  return retval;
}

ce qui fait d'un nu std::function de retour R dans un nullary packaged_task, puis l'ajoute aux tâches de la file d'attente. Notez que les tâches de la file d'attente doit être déplacez-savoir, parce qu' packaged_task se déplacer seule.

Note 1: je ne suis pas tout ce que familier avec std::future, de sorte que le ci-dessus pourrait être dans l'erreur.

Note 2: Si les tâches de la mettre dans le ci-dessus décrite, la file d'attente sont dépendantes les unes des autres pour des résultats intermédiaires, la file d'attente peuvent impasse, car aucune disposition de "récupérer" les threads sont bloqués et exécuter le nouveau code est décrit. Cependant, "nu de calcul de la" non-blocage des tâches devrait fonctionner correctement avec le modèle ci-dessus.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X