2012-05-15 1 views
1

Je ne mesure peut-être pas correctement mais j'ai un code simple avec lequel je joue. Je ne pense pas que c'est un pool de threads parce que si je rends l'unité de travail très grande alors le processeur va à 190-199% (j'ai dual core) mais si je baisse, l'unité de travail est plus petite exécute le programme à 140-160%. Je pense que les threads ne sont pas regroupés mais sont détruits/créés en cas de besoin, ce qui ralentit le programme lorsque les charges de travail sont plus petites. J'utilise tbb::task_scheduler_init pour contrôler le nombre de threads mais je ne sais pas comment dire à tbb de garder le thread en vie.Comment créer un pool de threads en C++ TBB?

est ici un code pour illustrer le problème:

#include <iostream> 
#include <list> 
#include <tbb/task.h> 
#include <tbb/task_group.h> 
//#include <stdlib.h> 
#include "tbb/task_scheduler_init.h" 
#include <boost/thread.hpp> 

using namespace tbb; 


long fib(long a) 
{ 
    if (a < 2) return 1; 

    return fib(a - 1) + fib(a - 2); 
} 

class PrintTask 
{ 
public: 
    void operator()() 
    { 
     //std::cout << "hi world!: " << boost::this_thread::get_id() << std::endl; 

     fib(10); 
    } 
}; 

int main() 
{ 
    tbb::task_scheduler_init init(4); //creates 4 threads 
    task_group group; 


    for (int i = 0; i < 2000000; ++i) 
    { 
     group.run(PrintTask()); 
     //std::cout << i << std::endl; 
    } 

    std::cout << "done" << std::endl; 
    group.wait(); 

    return(0); 
} 

si vous changez fib à 40-45, le travail pour chaque fil devient grande de sorte qu'il frappe pleine vitesse, mais si vous utilisez la configuration actuelle puis la les emplois sont très petits mais ils en font beaucoup.

note: Une chose que j'ai remarquée peut-être liée est que dans le cas ci-dessus, il utilise complètement ma mémoire (4 concerts gratuits). Le ralentissement pourrait-il être lié à cela? Aussi pourquoi ce programme prendrait-il toute la mémoire? Que stocke-t-il en mémoire, si je n'appelle que le thread, n'y a-t-il pas une file d'attente lui indiquant combien de fois il doit s'exécuter ou est-ce qu'il sauvegarde le thread entier en mémoire pour s'exécuter?

Désolé pour les questions étranges, j'ai lu le tutoriel, mais je suis encore confus à son comportement (même si j'obtiens le résultat escompté).

Merci.

+0

Je ne suis pas tout à fait sûr de la question.Il est peu probable que TBB crée/détruise des threads, si vous pensez qu'il pourrait être vérifier le threadid. – Rick

+0

@Rick ahhh votre droit ... alors peut-être que j'ai un malentendu plus profond de threads, car pourquoi un travail plus petit le fait courir plus lentement? Je ne partage aucune donnée entre les threads, donc le verrouillage ne devrait pas être un problème. la seule chose à laquelle je pouvais penser était que les threads prenaient du temps à démarrer (mais à droite, les identifiants des threads sont les mêmes). – Lostsoul

+0

lorsque j'essaye l'équivalent Java avec un pool de threads et 'executor.submit' pour envoyer du travail, alors il semble fonctionner constamment à près de 190% + – Lostsoul

Répondre

4

Vous ne devriez pas trop vous inquiéter de l'utilisation de l'UC, mais plutôt du temps d'exécution et de l'accélération par rapport à l'utilisation séquentielle.

Il y a quelques choses que vous devez comprendre au sujet de TBB:

Vous pouvez penser à la surcharge de la planification d'une tâche comme étant essentiellement constante (il est pas tout à fait, mais il est assez proche). Plus la quantité de travail que vous planifiez est petite, plus elle s'approche de cette constante. Si vous vous rapprochez de cette constante, vous ne verrez pas d'accélération.

De même, les threads restent inactifs lorsqu'ils ne trouvent pas de travail. Je suppose que lorsque vous appelez 'fib (10)', le coût de l'appel lancé et les allocations de tâches nécessaires approchent le coût de l'exécution du travail. Plus précisément, si vous avez vraiment 2000000 éléments avec la même tâche, vous devriez probablement appeler parallel_for ou parallel_for_each.

-Rick

+0

Merci beaucoup Rick. Je ne sais vraiment pas comment aborder mon problème alors ... J'ai posé une question sur la façon de gérer les charges de travail changeantes (c'est-à-dire lorsque vous avez des pools de threads qui consomment une file d'attente). fais ce que je voulais (sauf la question des frais généraux) parce que parallel_for (je crois) a besoin de savoir combien de travail est requis à l'avance. ma charge de travail est très similaire à cette question (beaucoup de petits travaux qui produisent/consomment la même file d'attente). En utilisant tbb, y a-t-il un meilleur moyen de le configurer? – Lostsoul

2

Juste pour souligner dernier point de Rick, le docs pour task_group Etat:

ATTENTION: Création d'un grand nombre de tâches pour un seul task_group est pas évolutive, parce que la création de tâches devient un goulot d'étranglement en série. Si crée plus d'un petit nombre de tâches simultanées, vous pouvez utiliser parallel_for (4.4) ou parallel_invoke (4.12) à la place, ou structurer le spawning en tant qu'arborescence récursive.

je fait ajouter parallel_do à cette liste probablement applicable à votre savoir-ne-nombre-de-tâches à l'avance des cas d'utilisation. Ses arguments incluent un "chargeur" ​​qui peut ajouter plus de tâches; mais encore une fois noter les commentaires dans les documents ne traitent pas des tâches une à la fois. Citation sélective:

Concevez votre algorithme de telle sorte que le corps ajoute souvent plus de pièce de travail. ... Pour atteindre l'accélération, la granulométrie de B :: operator() doit être de l'ordre d'au moins ~ 100 000 cycles d'horloge. Sinon, les frais généraux internes de parallel_do submergent le travail utile.

Si vous cherchez des exemples de TBB non trivial (au-delà parallel_for et tutorial etc), le TBB patterns manual est très bon.

+0

Merci beaucoup timday, vous m'avez intéressée à parallel_do. Je suis un peu confus sur la façon de faire ça. Dois-je d'abord lui donner une gamme de données à traiter (à partir d'une liste/liste) et ensuite utiliser l'option de chargeur pour ajouter continuellement plus de travail à cela? – Lostsoul

+0

parallel_do est passé des tâches individuelles de la plage spécifiée à parallel_do, plutôt qu'une plage réelle comme le "body" de parallel_for. Cela a des implications sur la façon dont les tâches individuelles doivent être calculées, sinon les frais généraux domineront. Les ping-ponging parallel_fors d'Anton sur votre autre question http://stackoverflow.com/a/10608826/24283 pourraient être une meilleure solution dans le cas de nombreuses tâches minuscules, du point de vue de la minimisation des frais généraux par tâche. – timday

Questions connexes