Quand un moteur de tâches parallèles devient-il une bonne solution?

8

Je suis souvent tenté de casser le jeu sur lequel je travaille pour essayer une architecture basée sur des tâches parallèles, mais cela ne semble pas être une grande exigence pour mon projet, donc je l'évite pour le moment. J'ai l'intention de l'essayer d'abord dans un projet de jouet, pour "jouer avec le concept".

Maintenant, ce que je demande, c'est que comme beaucoup de jeux (non AAA) ne nécessitent pas vraiment de très hautes performances, on a l'impression que l'utilisation d'un moteur basé sur les tâches ne vaut pas la peine d'être dérangé jusqu'à ... quels cas?

Pour le moment, je suppose seulement que c'est nécessaire lorsque vous avez vraiment besoin d'exploiter les performances maximales du matériel (multicœur). Y a-t-il d'autres cas où c'est une bonne idée d'utiliser un moteur basé sur les tâches? Ou peut-être, pour les nouveaux projets, il est toujours bon de commencer avec un moteur basé sur les tâches?

Klaim
la source

Réponses:

7

Le filetage est utilisé dans deux situations:

  1. Lorsque vous avez du travail à faire mais que vous avez besoin de réactivité pour être maintenu.
  2. Lorsque vous avez besoin de la puissance de plusieurs cœurs de traitement sur votre plate-forme préférée / exigence minimale.

Si vous pouvez raisonnablement vous attendre à ce que l'une ou les deux choses soient nécessaires, allez-y. Sinon, non. Bien sûr, personne ne vous empêchera de fileter pour le plaisir parce que vous le pouvez, ou pour fouiller et enquêter, mais en termes de filetage pour les avantages de gagner réellement une application multithread, les deux cas d'utilisation ci-dessus sont à peu près tout.

DeadMG
la source
2
+1 pour la réactivité: l'une des exigences de mon projet principal est de ne pas avoir d'écran de chargement, une expérience continue pour une immersion maximale. C'est un bon point.
Klaim
1
+1 exactement pourquoi j'ai utilisé plusieurs threads dans un jeu de puzzle: travail lourd (vérification de millions de combinaisons) sans geler l'interface utilisateur.
ashes999
"pas d'écran de chargement, expérience continue" - vous devrez construire votre architecture entière autour de cette idée, c'est une rupture avec la conception "charger un niveau et y jouer, puis charger un autre niveau" que la plupart des petits moteurs utilisent.
Patrick Hughes
14

Si votre jeu va être gourmand en matériel à distance, vous avez besoin de threads pour faire face à tout le matériel moderne; les futurs processeurs qui sortiront dans un an ou deux commenceront à faire de 4 cœurs le minimum et jusqu'à 16 cœurs communs pour les marchés passionnés / de performance. Si vous faites du multi-threading, faites une architecture orientée tâche, car tout autre modèle de threading est intrinsèquement cassé pour les moteurs de jeu prospectifs.

Maintenant, gardez à l'esprit que par «tâches», je veux dire «travaux» et non «threads séparés pour différents sous-systèmes de moteur». Vous ne voulez absolument pas faire quelque chose comme avoir un thread graphique, un thread physique, un thread AI, etc. Cela ne s'étend pas au-delà d'une petite poignée de cœurs et cela ne vous apporte en fait aucun véritable parallélisme de toute façon. La physique ne doit pas exécuter plus d'une mise à jour par mise à jour de l'IA (vous voulez que votre IA puisse réagir aux événements physiques), et les graphiques n'ont presque rien de nouveau à rendre si la physique n'a pas fonctionné, donc chaque sous-système s'exécute naturellement dans un ordre ordre. Tu ne

Ce que vous voulez, c'est faire. Créez une bobine de fil. Exécutez votre boucle de jeu avec la séquence classique de mises à jour de sous-système. Cependant, pour chaque sous-système, séparez la charge de travail en lots distincts séparables et distribuez-les au pool de threads. Attendez que tous les travaux soient terminés avant d'exécuter le prochain état de la boucle de mise à jour du jeu. Certains sous-systèmes peuvent avoir plusieurs sous-étapes; par exemple, les graphiques peuvent émettre une série de travaux pour effectuer l'abattage puis une deuxième série de travaux pour effectuer la création de la file d'attente de rendu. Cette approche évite le problème de synchronisation de la première approche, s'adapte à un plus grand nombre de cœurs et est franchement plus facile à coder, à déboguer et à maintenir.

Sean Middleditch
la source
Merci pour les conseils sur la façon de construire ce type de moteur, mais je suis déjà bien documenté, donc ce n'était pas nécessaire. C'est plus la question «quand ça vaut le coup» que je pose. Je pense que c'est toujours une bonne chose de pointer ces informations pour ceux qui ne savent pas, alors ne les supprimez pas :)
Klaim
Cela semble très raisonnable
Den
Tout comme Apple, une fois que vous vous êtes habitué aux emplois, vous ne pouvez plus revenir en arrière =) Ceci est une bonne suggestion d'architecture pour tout nouveau moteur.
Patrick Hughes
@SeanMiddleditch Ne changez-vous pas simplement la granularité du filetage de cette façon? Au lieu d'un seul thread par système (qui n'est en effet pas si évolutif, certains systèmes sont beaucoup plus gourmands en énergie que d'autres et il peut également y avoir beaucoup plus de systèmes que de cœurs disponibles), vous faites potentiellement plusieurs threads par système. Vous affinez donc la granularité des données sur lesquelles chaque thread agit. Je ne sais pas comment cela se déroulera, mais je n'ai rien de mieux à ajouter non plus. Avez-vous toujours cette opinion après 7 ans? Merci.
Nikos
1
@ Nik-Lz: non, car "plusieurs threads par système" implique que vous savez ce que les threads par système devraient être. Devriez-vous avoir 2 threads pour le rendu et 3 pour la physique? Combien pour l'IA? Cela ne dépendrait-il pas tous de la scène spécifique et même de l'endroit où le joueur regarde et de ce qui se passe en ce moment? Un système de tâches s'adapte facilement et dynamiquement aux besoins immédiats plutôt que de regrouper un thread entier dans un sous-système qui peut ne pas en avoir besoin pour le moment.
Sean Middleditch le
0

Le multithreading a deux utilisations, l'une pour améliorer les performances de votre programme et l'autre pour laisser le programme s'exécuter lorsqu'un processus important est en cours. par exemple, lorsque vous essayez de charger des données, cela dérange si votre programme raccroche, vous pouvez donc utiliser un autre thread pour le chargement et garder le thread principal libre pour continuer la boucle principale. améliorer les performances à l'aide de threads est en revanche quelque chose de vraiment difficile. car en général, vous faites tout dans un processus linéaire et cela contraste avec le traitement parallèle. et vous devez toujours utiliser votre thread principal pour les mises à jour graphiques des périphériques, ce qui rend encore plus difficile la répartition des tâches entre les threads.

Ali1S232
la source
1
L'enfilage n'est pas difficile. :) C'est juste quelque chose que beaucoup de gens n'apprennent jamais à faire correctement, alors ils pensent que c'est difficile à cause de l'inexpérience. Surtout dans les jeux, de nombreux algorithmes et structures de données de base sont assez faciles à gérer avec plusieurs threads. Les mises à jour physiques, par exemple, peuvent être divisées en îlots d'objets, et chacune est exploitée sur un thread différent pour l'intégration et la résolution. De même, l'élimination des objets pour les graphiques est à peu près complètement exempte de conflits d'accès aux données en lecture-écriture.
Sean Middleditch
@seanmiddleditch Je n'ai pas dit qu'il était difficile d'utiliser des threads en soi, mais réussir à utiliser les threads efficacement et les équilibrer pour partager la même charge est quelque chose de difficile.
Ali1S232