Des programmes qui prétendent qu'ils ne sont pas «multi-core» amicaux

17

Vous voyez cette phrase ou similaire lancée de temps en temps, se référant généralement à un programme qui prétend qu'ils n'ont pas été conçus pour tirer pleinement parti des processeurs multicœurs. Ceci est courant en particulier avec la programmation de jeux vidéo. (bien sûr, de nombreux programmes n'ont pas de simultanéité et n'en ont pas besoin, comme les scripts de base, etc.).

Comment se peut-il? De nombreux programmes (en particulier les jeux) utilisent intrinsèquement la concurrence, et puisque le système d'exploitation est en charge de la planification des tâches sur le CPU, ces programmes ne tirent-ils pas intrinsèquement parti des multiples cœurs disponibles? Que signifierait dans ce contexte "profiter de plusieurs cœurs"? Ces développeurs interdisent-ils réellement la planification des tâches du système d'exploitation et forcent-ils l'affinité ou leur propre planification? (Cela ressemble à un problème majeur de stabilité).

Je suis un programmeur Java, alors peut-être que je n'ai pas eu à faire face à cela en raison d'abstractions ou de tout ça.

SnakeDoc
la source
11
Une grande possibilité est que des raccourcis ont été pris dans la synchronisation, qui fonctionnent pour un système à processeur unique / cœur mais rompent avec la véritable concurrence de plusieurs processeurs / cœurs.
Bart van Ingen Schenau
@BartvanIngenSchenau: C'est correct. Vous devez développer cela et l'afficher comme réponse. Je pense que tous les autres ont raté le point.
kevin cline
1
Je pense que @Bart est vraiment proche. Cependant, s / work / semble fonctionner / et il sera plus proche de la marque.
Ben Voigt
en passant - j'en ai fait l'expérience en tant qu'utilisateur plutôt qu'en tant que programmeur - Ground Control 2 sur Windows XP. Je devais définir l'affinité du cœur à un seul cœur sur un système multicœur pour qu'il fonctionne correctement, sinon toutes les animations (en fait tout le jeu) s'exécuteraient à une vitesse 10x, ce qui, tout en étant plus difficile, devenait légèrement ennuyeux après un certain temps . Je n'ai pas fait de travail sur les jeux mais à mon avis, une partie du jeu semblait reposer sur le processeur qui ne faisait qu'un certain travail en même temps.
jammypeach

Réponses:

28

Une bonne concurrence nécessite beaucoup plus que de jeter quelques fils dans une application et d'espérer le meilleur. Il existe une gamme dans la façon dont un programme peut passer simultanément d'un parallèle embarrassant à un séquentiel pur. Tout programme donné peut utiliser la loi d' Amdahl pour exprimer à quel point un problème ou un algorithme est évolutif. Quelques qualifications pour une candidature parallèle embarrassante seraient:

  • Pas d'état partagé, chaque fonction ne dépend que des paramètres passés
  • Pas d'accès aux périphériques physiques (cartes graphiques, disques durs, etc.)

Il existe d'autres qualifications, mais avec seulement ces deux, nous pouvons comprendre pourquoi les jeux en particulier ne sont pas aussi faciles que vous pourriez penser pour tirer parti de plusieurs cœurs. D'une part, le modèle du monde qui sera rendu doit être partagé car différentes fonctions calculent la physique, le mouvement, appliquent l'intelligence artificielle, etc. Deuxièmement, chaque image de ce modèle de jeu doit être rendue à l'écran avec une carte graphique.

Pour être juste, de nombreux fabricants de jeux utilisent des moteurs de jeux produits par des tiers. Cela a pris du temps, mais ces moteurs de jeux tiers sont maintenant beaucoup plus parallèles qu'auparavant.

Il existe de plus grands défis architecturaux pour gérer une concurrence efficace

La concurrence peut prendre plusieurs formes, de l'exécution de tâches en arrière-plan à une prise en charge architecturale complète de la concurrence. Certaines langues vous offrent des fonctionnalités de concurrence très puissantes telles que ERLANG , mais cela vous oblige à penser très différemment à la façon dont vous construisez votre application.

Tous les programmes n'ont pas vraiment besoin de la complexité d'un support multicœur complet. Un tel exemple est le logiciel d'impôt, ou toute application pilotée par formulaire. Lorsque la plupart de votre temps est consacré à attendre que l'utilisateur fasse quelque chose, la complexité des applications multithreads n'est tout simplement pas très utile.

Certaines applications se prêtent à une solution parallèle plus embarrassante, comme les applications Web. Dans ce cas, la plate-forme démarre de manière embarrassante en parallèle et c'est à vous de ne pas avoir à imposer de conflit de thread.

En bout de ligne:

Toutes les applications ne sont pas vraiment blessées en ne profitant pas de plusieurs threads (et donc des cœurs). Pour ceux qui en souffrent, parfois les calculs ne sont pas adaptés au traitement parallèle ou le surcoût pour le coordonner rendrait l'application plus fragile. Malheureusement, le traitement parallèle n'est toujours pas aussi facile qu'il devrait l'être pour bien faire.

Berin Loritsch
la source
Ceci est une excellente analyse. Une chose qui me dérange cependant est votre point de vue sur le fait que les programmes du monde réel ne sont souvent pas embarrassants en parallèle et donc difficiles à paralléliser: bien qu'il puisse être impossible de faire la même chose en parallèle, il peut être très facile de faire différentes choses en parallèle ( par exemple dans une architecture de pipeline, ou avec un thread d'interface utilisateur distinct).
amon
8
Le vrai point est que vous devez concevoir pour une exécution parallèle, et si vous ne le faites pas, vous êtes contraint par votre manque de conception. Je suis d'accord qu'il peut être très facile de faire différentes choses en parallèle, mais pas s'il s'agit d'une application existante avec des attentes élevées des utilisateurs. Dans ce cas, il peut très bien avoir besoin d'une réécriture pour le rendre possible. Les réécritures sont intrinsèquement risquées, mais vous pouvez parfois leur présenter un argument valable. J'ai effectué quelques réécritures de ce type qui ont maximisé le traitement parallèle tout en préservant le plus de code possible. Il y a beaucoup de facteurs cachés.
Berin Loritsch
Très bonne réponse. Il peut être utile de souligner que non seulement il peut y avoir des rendements décroissants dans la parallélisation de certains systèmes, mais certains peuvent en fait devenir plus lents en raison des frais généraux nécessaires pour les rendre parallèles. En particulier, de nombreux sémaphores / verrous et changements de contexte peuvent avoir des effets négatifs sur l'exécution. Le changement de contexte en particulier pourrait réduire l'efficacité du cache, ce qui n'est pas un problème si vous êtes sur le point d'optimiser votre système. L'exemple d'OP de moteurs de jeu en particulier me conduit à me souvenir d'avoir entendu beaucoup plus sur l'optimisation de la mise en cache que l'accès parallèle.
Gankro
35

De nombreux programmes (en particulier les jeux) utilisent de manière inhérente la concurrence,

Non, en fait c'est l'inverse. La plupart des applications sont écrites dans un seul état d'esprit, et le ou les développeurs n'ont jamais apporté les modifications nécessaires pour prendre en charge la concurrence.

En C, C ++ et C #, vous devez indiquer explicitement à l'application de démarrer de nouveaux threads et / ou processus.

Je pense que vous vous concentrez trop sur la planification des threads et pas assez sur la gestion des données dans les threads potentiels. Le partage de données entre des threads et / ou des processus nécessite une certaine forme de synchronisation. Si vous modifiez une application pour utiliser plusieurs threads mais que cette synchronisation n'est pas en place, vous verrez probablement beaucoup de difficultés à détecter les bogues dans le code.

Pour les applications multi-thread sur lesquelles j'ai travaillé, je ne me suis généralement jamais soucié de la répartition et seulement de la synchronisation des données. Les seules fois où je devais me soucier de la répartition, c'était lorsque je cherchais les conditions de course en raison d'une mauvaise synchronisation des données.

Généralement, lorsqu'une application dit qu'elle ne peut pas utiliser plusieurs cœurs, cela signifie qu'elle n'a pas la synchronisation en place pour protéger la manipulation des données.


la source
Cela est vrai même pour les nouveaux programmes modernes de grands développeurs / éditeurs? Lorsque je m'assois et que j'écris un programme, l'une des premières choses à l'étape de la conception à laquelle je pense est la suivante: ai-je besoin de l'accès simultané? Parce que cela peut entraîner une conception radicalement différente. Les jeux en particulier doivent avoir un certain niveau de concurrence, sinon le jeu se bloquerait lorsque l'un des mille modèles à l'écran tenterait de faire quelque chose ...?
SnakeDoc
5
@SnakeDoc - Je pense que vous confondez vos domaines là-bas. Les sociétés de Big Game écrivent certainement avec la simultanéité à l'esprit, mais je n'ai pas encore vu un jeu d'une grande société qui ne prend pas en charge la simultanéité. Les applications et les jeux que j'ai vus qui ne peuvent pas prendre en charge la concurrence proviennent généralement de petits magasins / développeurs individuels où ils n'auraient pas commencé avec cet état d'esprit. Et à un moment donné de l'évolution de l'application, il devient impossible de se verrouiller en simultané après coup. Et certaines applications n'ont jamais été conçues pour en faire assez pour justifier leur simultanéité.
Et aussi certains jeux prospèrent sur de nouveaux contenus (graphiques et gameplay), sans avoir à mettre à jour le moteur de jeu (implémentation de code). Ainsi, le moteur de jeu pourrait avoir des années de retard dans la technologie.
rwong
6
@SnakeDoc: Vous n'avez pas besoin de simultanéité pour gérer des milliers de modèles à l'écran. Ce n'est pas comme si chaque objet de votre jeu avait besoin de son propre thread pour le simuler; un thread peut gérer les mises à jour de tout à l'écran à chaque pas de temps.
user2357112 prend en charge Monica
13

Il ne s'agit pas tant de plusieurs cœurs que de plusieurs threads. Le système d'exploitation peut planifier l'exécution d'un thread sur le cœur qu'il souhaite, et cette planification est transparente pour le programme en cours de planification. Cependant, de nombreux programmes ne sont pas écrits à l'aide de plusieurs threads, ils ne peuvent donc s'exécuter que sur un seul cœur à la fois.

Pourquoi devrais-je écrire un programme monothread? Ils sont plus faciles à écrire et à déboguer: une chose se produit après l'autre (au lieu de plusieurs choses se produisant à la fois et pouvant se gêner mutuellement). Ou votre programme ne cible peut-être pas les machines multicœurs (comme c'était le cas avec les anciens jeux). Dans certains cas, un programme multithread peut même fonctionner plus lentement qu'une version monothread si la surcharge des commutateurs de contexte et de la communication entre les threads l'emporte sur la vitesse gagnée par l'exécution parallèle (certaines parties du programme peuvent ne pas être parallélisables).

amon
la source
8

Ce n'est pas une réponse complète. C'est un récit édifiant.

Un jour, j'ai pensé montrer aux étudiants de mon cours de programmation concurrente un tri rapide parallèle. Quicksort devrait bien paralléliser, je pensais. J'ai utilisé deux fils. Je l'ai exécuté sur mon ordinateur monocœur. Les résultats ont été:

  • 14 secondes pour une version simple thread.
  • 15 secondes pour la version à 2 fils.

C'était à peu près ce à quoi je m'attendais.

Ensuite, je l'ai essayé sur une machine dual-core plus récente.

  • 11 secondes pour la version simple thread.
  • 20 secondes pour la version à 2 fils.

Les deux threads partageaient une file d'attente des tâches restantes. Il semble que les champs de l'objet de file d'attente soient mélangés d'avant en arrière entre le cache d'un cœur et celui de l'autre.

Theodore Norvell
la source
2
Avec combien d'éléments de tableau avez-vous testé? Mergesort serait peut-être plus approprié car la programmation multicœur aurait nécessité la copie de données pour éviter les conflits de ligne de cache?
rwong
2
@rwong Il y avait 10 000 000 d'éléments de tableau. Mergesort serait certainement bien parallèle. Si j'avais utilisé le tri par fusion, je n'aurais probablement pas tiré de leçon utile.
Theodore Norvell
1
@ArlaudPierre J'envisagerai de paralléliser n'importe quel algorithme. Quicksort est intéressant car vous pouvez utiliser l'approche du sac de tâches pour cela. Comme les tâches sont indépendantes, mon intuition était que ce devrait être un exemple de parallélisme embarrassant. Je dois mentionner que, après un peu de réglage, il a effectivement obtenu une accélération de près de 2.
Theodore Norvell
1
@Jules La réponse est l'équilibrage de charge. Je voulais aussi l'écrire d'une manière qui facilite le changement du nombre de threads. Votre approche se généralise bien aux puissances de 2, mais pas si bien aux autres nombres de threads.
Theodore Norvell
2
@MaciejPiechotka La morale est à peu près tout ce que vous suggérez. Mais pour en revenir à l'OP, je pense que la morale la plus pertinente est qu'un programme multithread peut en fait s'exécuter (beaucoup) plus lentement sur une architecture multicœur que sur un processeur monocœur, à moins que des efforts aient été déployés pour garantir le contraire.
Theodore Norvell