Pourquoi utiliser plus de threads le rend plus lent que d'utiliser moins de threads

30

J'ai essayé d'exécuter le programme X en utilisant 8 threads et c'était fini en n minutes .
J'ai essayé d'exécuter le même programme en utilisant 50 threads et c'était fini en n * 10 minutes .

Pourquoi cela se produit-il et comment puis-je obtenir un nombre optimal de threads que je peux utiliser?

PoGibas
la source

Réponses:

33

C'est une question compliquée que vous posez. Sans en savoir plus sur la nature de vos fils, c'est difficile à dire. Quelques éléments à considérer lors du diagnostic des performances du système:

Est le processus / fil

  • Lié au CPU (nécessite beaucoup de ressources CPU)
  • Mémoire liée (nécessite beaucoup de ressources RAM)
  • E / S liées (ressources réseau et / ou disque dur)

Ces trois ressources sont toutes finies et chacune peut limiter les performances d'un système. Vous devez voir lequel (peut-être 2 ou 3 ensemble) consomme votre situation particulière.

Vous pouvez utiliser ntopet iostatet vmstatpour diagnostiquer ce qui se passe.

slm
la source
8
Le matériel compte aussi. Physique, virtuel, nombre de cœurs, type de cœur, cache L1 / L2 / L3, etc.
EightBitTony
46

"Pourquoi cela arrive-t-il?" est assez facile à répondre. Imaginez que vous ayez un couloir pouvant accueillir quatre personnes côte à côte. Vous voulez déplacer toutes les ordures à une extrémité, à l'autre extrémité. Le nombre de personnes le plus efficace est de 4.

Si vous avez 1 à 3 personnes, vous manquez d'utiliser un espace de couloir. Si vous avez 5 personnes ou plus, alors au moins une de ces personnes est toujours coincée derrière une autre personne. Ajouter de plus en plus de gens obstrue simplement le couloir, cela n'accélère pas l'activité.

Donc, vous voulez avoir autant de personnes que possible sans provoquer de file d'attente. Pourquoi vous faire la queue (ou les goulots d' étranglement) dépend des questions dans la réponse de slm.

EightBitTony
la source
1
Votre exemple est trompeur. Il serait préférable de dire quelque chose comme: "Vous avez un couloir où vous pouvez loger quatre personnes côte à côte et il est utilisé par vous et d' autres personnes pour différentes tâches. Il y a un arbitre qui décide qui peut passer par le couloir . Ensuite, le nombre de personnes le plus efficace est supérieur à 4 et inférieur à un certain nombre, où vos personnes commencent à faire la queue [très dépendant du contexte]. " Habituellement, le fait d'avoir quelques threads de plus que le nombre de CPU fonctionne mieux que d'utiliser exactement 4 threads. Si vous êtes le seul à utiliser le CPU, alors 4c'est le meilleur numéro.
Bakuriu
7
Excellent exemple, +1. Bakuriu, c'est un exemple qui illustre le problème d'une ressource partagée limitée. Cela explique le problème, pas comment trouver le nombre optimal de threads.
Bananguin
1
Il serait également utile de garder à l'esprit que les threads ont toujours leur propre type de changement de contexte qui continue. L'augmentation du nombre de threads n'augmente pas la capacité de performance (comme vous l'avez souligné) mais elle draine également le temps CPU en donnant au noyau plus de travail à faire. Fondamentalement, il y a des rendements décroissants sur le filetage et en faire trop entraîne des performances rétrogrades.
Bratchley
9
Chaque problème peut être décrit à plusieurs niveaux de complexité. J'ai proposé une approximation du problème, qui je pense est utile pour expliquer les bases. Bien sûr, il peut être plus raffiné et plus détaillé, mais plus vous le faites en détail, moins il est utile comme introduction au problème.
EightBitTony
J'ajouterais simplement qu'au lieu de passer beaucoup de temps à calculer le nombre optimal de threads, il suffit de le coder pour qu'il puisse être modifié facilement. Toute fusion importante comme celle-ci nécessitera de nombreux tests (la plupart avec de petits sous-ensembles de vos données) pour se perfectionner. Augmentez le nombre de threads jusqu'à ce que vous constatiez une baisse importante des performances ou un impact sur une autre activité du système est inacceptable.
DocSalvager
20

Une recommandation courante est n + 1 threads, n étant le nombre de cœurs CPU disponibles. De cette façon, n threads peuvent faire fonctionner le CPU pendant qu'un thread attend les E / S disque. Avoir moins de threads n'utiliserait pas pleinement la ressource CPU (à un moment donné, il y aura toujours des E / S à attendre), avoir plus de threads entraînerait des conflits de threads sur la ressource CPU.

Les threads ne sont pas gratuits, mais avec des frais généraux comme des changements de contexte, et - si des données doivent être échangées entre des threads, ce qui est généralement le cas - divers mécanismes de verrouillage. Cela ne vaut le coût que si vous avez réellement plus de cœurs de processeur dédiés pour exécuter le code. Sur un processeur monocœur, un seul processus (pas de threads séparés) est généralement plus rapide que tout threading effectué. Les threads ne font pas comme par magie votre CPU aller plus vite, cela signifie juste un travail supplémentaire.

frostschutz
la source
Cela devrait être la réponse générale étant donné la quantité d'informations disponibles en question. nous n'avons pas besoin d'une thèse et d'une philosophie complètes comme les autres réponses
Allahjane
9

Comme d' autres l' ont souligné ( slm réponse , EightBitTony réponse ) c'est une question et d' autant plus que vous ne décrivez pas compliqué ce que vous faites et thred comment ils le font.

Mais jeter définitivement plus de fils peut aggraver les choses.

Dans le domaine du calcul parallèle, il y a la loi d'Amdahl qui peut être applicable (ou pas, mais vous ne décrivez pas les détails de votre problème, donc ...) et peut donner un aperçu général de cette classe de problèmes.

Le point de la loi d'Amdahl est que dans tout programme (dans n'importe quel algorithme), il y a toujours un pourcentage qui ne peut pas être exécuté en parallèle (la partie séquentielle ) et il y a un autre pourcentage qui peut être exécuté en parallèle (la partie parallèle ) [Évidemment ces deux portions totalisent 100%].

Ces portions peuvent être exprimées en pourcentage du temps d'exécution. Par exemple, 25% du temps peut être consacré à des opérations strictement séquentielles et les 75% restants sont consacrés à des opérations pouvant être exécutées en parallèle.

Image de Wikipedia (Image de Wikipedia )

La loi d'Amdahl prévoit que pour chaque portion parallèle donnée (par exemple 75%) d'un programme, vous ne pouvez accélérer l'exécution que jusqu'à présent (par exemple au plus 4 fois) même si vous utilisez de plus en plus de processeurs pour faire le travail.

En règle générale, plus vous programmez que vous ne pouvez pas transformer en exécution parallèle, moins vous pouvez obtenir en utilisant plus d'unités d'exécution (processeurs).

Étant donné que vous utilisez des threads (et non des processeurs physiques), la situation peut être encore pire que cela. N'oubliez pas que les threads peuvent être traités (en fonction de l'implémentation et du matériel disponible, par exemple CPU / Cores) partageant le même processeur / core physique (c'est une forme de multitâche, comme indiqué dans une autre réponse).

Cette prédiction théorique (sur les temps CPU) ne considère pas les autres goulots d'étranglement pratiques comme

  1. Vitesse d'E / S limitée (disque dur et "vitesse" réseau)
  2. Limites de taille de mémoire
  3. Autres

cela peut facilement être le facteur limitant dans les applications pratiques.

DavAlPi
la source
Cette réponse doit être sélectionnée.
Eonil
6

Le coupable ici devrait être le "CONTEXT SWITCHING". Il s'agit du processus d'enregistrement de l'état du thread actuel pour commencer à exécuter un autre thread. Si un certain nombre de threads reçoivent la même priorité, ils doivent être inversés jusqu'à ce qu'ils terminent l'exécution.

Dans votre cas, lorsqu'il y a 50 threads, beaucoup de changements de contexte ont lieu par rapport à l'exécution de 10 threads.

Cette surcharge de temps introduite à cause du changement de contexte est ce qui rend votre programme lent

x-treme
la source
Comme nous ne savons pas quels sont les fils, cela semble être une supposition. Oui, le changement de contexte ajoute une surcharge, mais si les threads font une sorte d'analyse de données, le problème pourrait être des problèmes de cache (c'est-à-dire ne pas pouvoir utiliser le cache car chaque fois que vous changez de threads, vous devez le vider).
EightBitTony
Le changement de contexte de thread en soi , à moins que nous ne soyons confrontés à un grand nombre de changements de contexte, n'aura probablement pas d'impact d'ordre de grandeur sur les performances. 50 threads est élevé mais pas extrême (sur ma boîte en ce moment, ps ax | wc -lrapporte 225 processus, et il n'est en aucun cas lourdement chargé). Je suis enclin à aller avec la supposition de @ EightBitTony; L'invalidation du cache est probablement un problème plus important, car chaque fois que vous videz le cache, le processeur doit attendre des éons pour le code et les données de la RAM.
un CVn le
3

Pour corriger la métaphore d'EightBitTony:

"Pourquoi cela arrive-t-il?" est assez facile à répondre. Imaginez que vous ayez deux piscines, une pleine et une vide. Vous souhaitez déplacer toute l'eau de l'un à l'autre et disposer de 4 seaux . Le nombre de personnes le plus efficace est de 4.

Si vous avez 1 à 3 personnes, vous manquez d'utiliser des seaux . Si vous avez 5 personnes ou plus, alors au moins une de ces personnes est coincée en attendant un seau . Ajouter de plus en plus de personnes ... n'accélère pas l'activité.

Vous voulez donc avoir autant de personnes que possible pour effectuer un certain travail (utiliser un seau) simultanément .

Une personne ici est un thread, et un compartiment représente la ressource d'exécution qui est le goulot d'étranglement. L'ajout de fils de discussion n'aide pas s'ils ne peuvent rien faire. De plus, nous devons souligner que le passage d'un seau d'une personne à une autre est généralement plus lent qu'une seule personne portant simplement le seau à la même distance. C'est-à-dire que deux threads à tour de rôle sur un noyau accomplissent généralement moins de travail qu'un seul thread s'exécutant deux fois plus longtemps: cela est dû au travail supplémentaire effectué pour basculer entre les deux threads.

Que la ressource d'exécution limitée (bucket) soit un processeur, un noyau ou un pipeline d'instructions hyper-threaded pour vos besoins dépend de la partie de l'architecture qui constitue votre facteur limitant. Notez également que nous supposons que les threads sont entièrement indépendants. Ce n'est le cas que s'ils ne partagent aucune donnée (et évitent toute collision avec le cache).

Comme deux personnes l'ont suggéré, pour les E / S, la ressource limitante pourrait être le nombre d'opérations d'E / S pouvant être mises en file d'attente: cela pourrait dépendre de toute une série de facteurs matériels et du noyau, mais pourrait facilement être beaucoup plus important que le nombre de noyaux. Ici, le changement de contexte qui est si coûteux par rapport au code lié à l'exécution, est assez bon marché par rapport au code lié aux E / S. Malheureusement, je pense que la métaphore deviendra complètement hors de contrôle si j'essaie de justifier cela avec des seaux.

Notez que le optimale comportement avec le code lié d' E / S est typiquement encore d'avoir au plus un fil par pipeline / core / CPU. Cependant, vous devez écrire du code d'E / S asynchrone ou synchrone / non bloquant, et l'amélioration relativement faible des performances ne justifiera pas toujours la complexité supplémentaire.


PS. Mon problème avec la métaphore du couloir d'origine est qu'elle suggère fortement que vous devriez pouvoir avoir 4 files d'attente de personnes, avec 2 files d'attente transportant des déchets et 2 revenant pour en collecter plus. Ensuite, vous pouvez faire chaque file d'attente presque aussi longtemps que le couloir, et l'ajout de personnes a accéléré l'algorithme (vous avez essentiellement transformé tout le couloir en tapis roulant).

En fait, ce scénario est très similaire à la description standard de la relation entre la latence et la taille de la fenêtre dans les réseaux TCP, c'est pourquoi il m'a sauté aux yeux.

Inutile
la source
Ce n'est pas une métaphore, c'est une approximation conçue pour expliquer le système aux gens de manière à pouvoir le visualiser facilement. En tant que tel, il sera toujours `` détraqué '' par des personnes qui connaissent le niveau de détail suivant, mais ne réalisent pas que leur niveau de détail n'est pas réellement nécessaire pour les débutants. Personne n'apprend la physique des particules en commençant au niveau du doctorat. Tout ce qui précède est une approximation, ils vous y conduisent progressivement, en l'affinant au fur et à mesure. Ce n'est pas «faux», ce n'est tout simplement pas l'image complète.
EightBitTony
Personne n'est confus sur la forme de discours que vous avez utilisée, et ce n'est pas une mauvaise analogie. Chaque analogie a une limite au-delà de laquelle elle s'écarte de la chose qu'elle est censée décrire et cesse d'être utile. Je ne l'ai mentionné que parce que l'original me rappelait si fortement un scénario différent, et parce que je ne pense pas que cette version soit plus complexe pour la prévision (espérons-le) améliorée.
Inutile
0

C'est assez simple et simple à comprendre. En ayant plus de threads que ce que votre CPU prend en charge, vous sérialisez et non parallélisez. Plus vous avez de threads, plus votre système sera lent. Vos résultats sont en fait une preuve de ce phénomène.

Bruno Taboada
la source