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.