Est-il possible d'écrire du code (ou un logiciel complet, plutôt qu'un morceau de code) qui ne fonctionnera pas correctement lorsqu'il est exécuté sur un processeur ayant moins de N nombres de cœurs? Sans le vérifier explicitement et échouer exprès:
SI (noOfCores <4) ALORS ne fonctionne pas correctement exprès
Je regarde la configuration système minimale d'un jeu (Dragon Age: Inquisition), et il indique un minimum d'un processeur à quatre cœurs. De nombreux joueurs disent qu'il ne fonctionne PAS sur les processeurs à deux cœurs et même sur Intel Core i3s avec deux cœurs physiques et deux cœurs logiques. Et ce n'est PAS un problème de puissance de calcul.
D'après ma compréhension, les threads sont complètement isolés du CPU par le système d'exploitation car cela ne peut pas être fait.
Juste pour clarifier les choses:
Je demande [~ # ~] pas [~ # ~] "Puis-je connaître le nombre de cœurs de processeur à partir du code et échouer exprès? " ... Un tel code serait mal intentionné (vous oblige à acheter un processeur plus cher pour exécuter un programme - sans avoir besoin de puissance de calcul). Je demande que votre code, par exemple, ait quatre threads et échoue lorsque deux threads sont exécutés sur le même noyau physique (sans vérifier explicitement les informations système et échouer à dessein) .
En bref, peut-il y avoir un logiciel qui nécessite plusieurs cœurs, sans avoir besoin d'une puissance de calcul supplémentaire provenant de plusieurs cœurs? Il faudrait simplement N cœurs physiques distincts.
Il peut être possible de le faire "par accident" avec une utilisation négligente de l'affinité de base. Considérez le pseudocode suivant:
Si vous démarrez quatre d'entre eux sur un processeur à deux cœurs, alors soit quelque chose ne va pas avec le paramètre d'affinité du cœur, soit vous vous retrouvez avec deux threads monopolisant les cœurs disponibles et deux threads qui ne sont jamais planifiés. À aucun moment, il n'a explicitement demandé combien de cœurs il y avait au total.
(Si vous avez des threads de longue durée, la définition de l'affinité CPU améliore généralement le débit)
L'idée que les sociétés de jeux "forcent" les gens à acheter du matériel plus cher sans raison valable n'est pas très plausible. Il ne peut que leur perdre des clients.
Edit: ce message a maintenant 33 votes positifs, ce qui est beaucoup étant donné qu'il est basé sur des suppositions éclairées!
Il semble que les gens ont obtenu DA: I pour fonctionner, mal, sur des systèmes dual-core: http://www.dsogaming.com/pc-performance-analyses/dragon-age-inquisition-pc-performance- analysis / Cette analyse mentionne que la situation s'améliore considérablement si l'hyperthreading est activé. Étant donné que HT n'ajoute plus d'unités de problème d'instructions ni de cache, il permet simplement à un thread de s'exécuter tandis qu'un autre est dans un blocage de cache, ce qui suggère fortement qu'il est lié uniquement au nombre de threads.
Une autre affiche affirme que la modification des pilotes graphiques fonctionne: http://answers.ea.com/t5/Dragon-Age-Inquisition/Working-solution-for-Intel-dual-core-CPUs/td-p/ 3994141 ; étant donné que les pilotes graphiques ont tendance à être une misérable ruche d'écume et de villany, ce n'est pas surprenant. Un ensemble notoire de pilotes avait un mode "correct et lent" contre "rapide et incorrect" qui était sélectionné s'il était appelé à partir de QUAKE.EXE. Il est tout à fait possible que les pilotes se comportent différemment pour différents nombres de CPU apparents. Peut-être (retour à la spéculation) un mécanisme de synchronisation différent est utilisé. Utilisation abusive de verrous tournants ?
"L'utilisation abusive des primitives de verrouillage et de synchronisation" est une source très, très courante de bogues. (Le bogue que je suis censé regarder au travail en écrivant ceci est "planter si la modification des paramètres de l'imprimante en même temps que le travail d'impression se termine").
Édition 2: les commentaires mentionnent un OS essayant d'éviter la famine des threads. Notez que le jeu peut avoir son propre quasi-planificateur interne pour affecter le travail aux threads, et il y aura un mécanisme similaire dans la carte graphique elle-même (qui est en fait un système multitâche propre). Les chances d'un bug dans l'un d'entre eux ou l'interaction entre eux sont assez élevées.
www.ecsl.cs.sunysb.edu/tr/ashok.pdf (2008) est une thèse de troisième cycle sur une meilleure planification des cartes graphiques qui mentionne explicitement qu'elles utilisent normalement la planification du premier arrivé, premier servi, qui est facile à mettre en œuvre dans systèmes non préemptifs. La situation s'est-elle améliorée? Probablement pas.
Il peut être nécessaire d'avoir 4 cœurs car l'application exécute quatre tâches dans des threads parallèles et s'attend à ce qu'elles se terminent presque simultanément.
Lorsque chaque thread est exécuté par un noyau distinct et que tous les threads ont exactement la même charge de travail de calcul, ils sont susceptibles (mais loin d'être garantis) de se terminer à peu près en même temps. Mais lorsque deux threads s'exécutent sur un noyau, le timing sera beaucoup moins prévisible car le noyau changera de contexte entre les deux threads tout le temps.
Les bogues qui se produisent en raison d'un timing de thread inattendu sont appelés " conditions de concurrence ".
Dans le contexte du développement de jeux, une architecture plausible avec ce type de problème pourrait être celle où différentes fonctionnalités du jeu sont simulées en temps réel par différents threads CPU. Lorsque chaque fonctionnalité s'exécute sur un noyau propre, elles sont toutes simulées à peu près à la même vitesse. Mais lorsque deux fonctionnalités s'exécutent sur un seul cœur, les deux ne seront simulées que moitié moins vite que le reste du monde du jeu, ce qui pourrait provoquer toutes sortes de comportements étranges.
Notez qu'une architecture logicielle qui dépend de threads indépendants fonctionnant avec des temporisations spécifiques est extrêmement fragile et signe d'une très mauvaise compréhension de la programmation simultanée. Il existe des fonctionnalités disponibles dans pratiquement toutes les API multithreads pour synchroniser explicitement les threads afin d'éviter ce type de problèmes.
Il est peu probable que ces "exigences minimales" représentent quelque chose en dessous duquel le jeu ne fonctionnera pas. Il est beaucoup plus probable qu'ils représentent quelque chose en dessous duquel le jeu ne fonctionnera pas avec des performances acceptables. Aucune entreprise de jeux ne veut traiter avec de nombreux clients qui se plaignent de performances nulles lorsqu'ils l'exécutent sur une boîte à 1 cœur à 1 GHz, même si le logiciel pourrait fonctionner techniquement. Donc, ils ont probablement délibérément conçu pour échouer dur sur des boîtes avec moins de cœurs que ce qui leur donnerait des performances acceptables.
Une mesure importante des performances du jeu est la fréquence d'images. En règle générale, ils fonctionnent à 30 ou 60 images par seconde. Cela signifie que le moteur de jeu doit restituer la vue actuelle à partir de l'état du jeu dans un laps de temps fixe. Pour atteindre 60 images par seconde, il a juste un peu plus de 16 ms pour ce faire. Les jeux avec des graphismes haut de gamme sont extrêmement liés au processeur et il y a donc un énorme compromis entre essayer de pousser une meilleure qualité (ce qui prend plus de temps) et la nécessité de rester dans ce budget. Ainsi, le budget-temps pour chaque trame est extrêmement serré.
Parce que le budget temps est serré, le développeur souhaite idéalement un accès exclusif à un ou plusieurs cœurs. Ils veulent aussi probablement être en mesure de faire leurs trucs de rendu dans un noyau, exclusivement, car c'est ce qui doit être fait avec ce budget temps, tandis que d'autres choses, comme le calcul de l'état du monde, se produisent sur un processus séparé où il ne le fera pas empiéter.
Vous pourriez, en théorie, regrouper tout cela sur un seul noyau, mais alors tout devient beaucoup plus difficile. Tout à coup, vous devez vous assurer que toutes ces informations sur l'état du jeu se produisent assez rapidement et permettent à votre rendu de se produire. Vous ne pouvez pas simplement en faire deux threads logiciels car il n'y a aucun moyen de faire comprendre au système d'exploitation "le thread A doit terminer X quantité de travail en 16 ms, quel que soit le thread B".
Les développeurs de jeux n'ont aucun intérêt à vous faire acheter du nouveau matériel. La raison pour laquelle ils ont des exigences système est que le coût de prise en charge des machines bas de gamme n'en vaut pas la peine.
Trois threads en temps réel qui ne dorment jamais et un autre thread. S'il y a moins de quatre cœurs, le quatrième thread ne s'exécute jamais. Si le quatrième thread doit communiquer avec l'un des threads en temps réel pour que le thread en temps réel se termine, le code ne se terminera pas avec moins de quatre cœurs.
De toute évidence, si les threads en temps réel attendent quelque chose qui ne leur permet pas de dormir (comme un verrou tournant), le concepteur du programme a foiré.
Tout d'abord, les threads logiciels n'ont rien à voir avec les threads matériels et sont souvent mélangés. Les threads logiciels sont des morceaux de code qui peuvent être distribués et exécutés seuls dans le contexte du processus. Les threads matériels sont principalement gérés par le système d'exploitation et sont envoyés au cœur du processeur lorsqu'il s'agit de programmes réguliers. Ces threads matériels sont distribués en fonction de la charge; le répartiteur de thread matériel agit plus ou moins comme un équilibreur de charge.
Cependant, en ce qui concerne les jeux, en particulier les jeux haut de gamme, les threads matériels sont parfois gérés par le jeu lui-même ou le jeu indique au répartiteur de threads quoi faire. En effet, chaque tâche ou groupe de tâches n'a pas la même priorité que dans un programme normal. Parce que l'âge du dragon provient d'un studio de jeu haut de gamme utilisant des moteurs de jeu haut de gamme, je peux imaginer qu'il utilise la répartition "manuelle" et que le nombre de cœurs devient une exigence système minimale. Tout programme se bloquait lorsque j'envoyais un morceau de code vers le 3e noyau physique exécuté sur une machine avec seulement 1 ou 2 cœurs.
Puisqu'il est possible d'utiliser virtualize pour avoir plus de cœurs virtuels que physiques et que le logiciel ne saurait pas qu'il fonctionne sur une virtualisation et pense plutôt qu'il a autant de cœurs physiques, je dirais qu'un tel logiciel n'est pas possible.
Autrement dit, il n'est pas possible d'écrire un logiciel qui s'arrêtera toujours sur moins de N cœurs.
Comme d'autres l'ont souligné, il existe des solutions logicielles qui peuvent potentiellement vérifier, surtout si le système d'exploitation et le code utilisés ont peu de protection contre les conditions de concurrence lorsque N processus s'exécutent sur <N processeurs. La vraie astuce est le code qui échouera lorsque vous avez moins de N processeurs mais qui n'échouera pas lorsque vous avez N processeurs mais avez un système d'exploitation qui peut affecter du travail à moins de N processeurs.
Il se peut qu'il y ait trois threads faisant quelque chose (générer des arrière-plans ou générer NPC mouvement) et passer des événements à un quatrième, qui est censé agréger/filtrer les événements et mettre à jour le modèle de vue. Si le quatrième thread n'obtient pas tous les événements (car il n'est pas planifié sur un noyau) alors le modèle de vue n'est pas mis à jour correctement. Cela ne peut se produire que sporadiquement, mais ces cœurs doivent être disponibles à tout moment. Cela pourrait expliquer pourquoi vous ne voyez pas une utilisation élevée du processeur tout le temps, mais le jeu ne fonctionne pas correctement de toute façon.
Je pense que Joshua se dirige sur la bonne voie, mais pas à sa conclusion.
Supposons que vous ayez une architecture où il y a trois threads qui sont écrits pour faire autant qu'ils le peuvent - quand ils finissent ce qu'ils font, ils le font à nouveau. Pour maintenir les performances, ces threads ne libèrent le contrôle de rien - ils ne veulent pas risquer le retard du planificateur de tâches Windows. Tant qu'il y a 4 cœurs ou plus, cela fonctionne bien, il échoue gravement s'il n'y en a pas.
En général, ce serait une mauvaise programmation, mais les jeux sont une autre affaire - lorsque vous êtes confronté à un choix entre une conception inférieure sur tout le matériel ou une conception supérieure sur un matériel suffisamment bon ou une défaillance sur des développeurs de jeux de matériel inférieur choisissent généralement pour exiger le matériel.
Is it possible to write code (or complete software, rather than a piece of code) that won't work properly when run on a CPU that has less than N number of cores?
Absolument. L'utilisation de threads en temps réel serait un bon exemple d'une situation dans laquelle c'est non seulement possible, mais la manière souhaitée (et souvent, la seule manière correcte) pour faire le travail. Cependant, les threads en temps réel sont généralement limités au noyau du système d'exploitation, généralement pour les pilotes qui doivent être en mesure de garantir qu'un événement matériel quelconque est géré dans une période de temps définie. Vous ne devriez pas avoir de threads en temps réel dans les applications utilisateur normales et je ne suis pas sûr qu'il soit même possible d'en avoir un dans une application en mode utilisateur Windows. Généralement, les systèmes d'exploitation rendent intentionnellement impossible de le faire à partir de la terre des utilisateurs précisément parce qu'ils permettent à une application donnée de prendre le contrôle du système.
Concernant les applications utilisateur: votre supposition selon laquelle la vérification d'un nombre donné de threads afin de s'exécuter est nécessairement intentionnelle est incorrecte. Par exemple, vous pourriez avoir 2 tâches de longue durée et exigeantes en performances qui ont besoin d'un noyau pour elles-mêmes. Quelle que soit la vitesse du cœur du processeur, le partage d'un cœur avec d'autres threads pourrait être une dégradation sérieuse et inacceptable des performances en raison du cache du cache ainsi que des pénalités normales encourues par le changement de thread (qui sont assez substantielles.) Dans ce cas, il serait parfaitement raisonnable, en particulier pour un jeu, pour définir chacun de ces threads pour avoir une affinité uniquement sur un noyau particulier pour chacun d'eux, puis définissez tous vos autres threads pour ne pas avoir d'affinité sur ces 2 cœurs. Pour ce faire, cependant, vous devez ajouter une vérification que le système a plus de 2 cœurs et échouer si ce n'est pas le cas.
Tout code utilisant des verrous tournants avec une quantité notable de conflits de verrouillage fonctionnera terriblement (dans une mesure où - pour une application comme un jeu - vous pouvez dire "ne fonctionne pas" ) si le nombre de threads dépasse le nombre de cœurs.
Imaginez par exemple un thread producteur soumettant des tâches à une file d'attente qui dessert 4 threads consommateurs. Il n'y a que deux cœurs:
Le producteur essaie d'obtenir le spinlock, mais il est détenu par un consommateur fonctionnant sur l'autre noyau. Les deux cœurs tournent en boucle pendant que le producteur tourne, attendant que la serrure soit libérée. C'est déjà mauvais, mais pas aussi mauvais que possible.
Malheureusement, le thread consommateur est à la fin de son quantum de temps, il est donc préempté et un autre thread consommateur est planifié. Il essaie de saisir le verrou, mais bien sûr, le verrou est pris, alors maintenant deux cœurs tournent et attendent quelque chose qui ne peut pas arriver.
Le thread producteur atteint la fin de sa tranche de temps et est préempté, un autre consommateur se réveille. Encore une fois, deux consommateurs attendent la libération d'un verrou, et cela n'arrivera pas avant que deux quantums de temps supplémentaires ne se soient écoulés.
[...] Enfin, le consommateur qui tenait le verrou tournant a relâché le verrou. Il est immédiatement pris par celui qui tourne sur l'autre noyau. Il y a 75% de chances (3 pour 1) que ce soit un autre fil de consommation. En d'autres termes, il est probable à 75% que le producteur soit toujours au point mort. Bien sûr, cela signifie également que les consommateurs ralentissent. Sans les tâches de sumbitting du producteur, ils n'ont rien à faire.
Notez que cela fonctionne en principe avec n'importe quel type de verrou, pas seulement avec des verrous tournants - mais l'effet dévastateur est beaucoup plus important avec les verrous tournants car le processeur continue de graver des cycles alors qu'il n'obtient rien.
Imaginez maintenant qu'en plus de ce qui précède, certains programmeurs ont eu la brillante idée d'utiliser un thread dédié avec une affinité définie pour le premier noyau, de sorte que RDTSC donnera des résultats fiables sur tous les processeurs (ce ne sera pas de toute façon, mais certaines personnes le pensent).