Exercices d'émulation: manuel Xbox 360 FMA


Il y a plusieurs années, j'ai travaillé dans le département Microsoft Xbox 360. Nous avons pensé à publier une nouvelle console et avons décidé que ce serait génial si cette console pouvait exécuter des jeux de la console de la génération précédente.

L'émulation est toujours difficile, mais elle l'est encore plus si vos chefs d'entreprise changent constamment les types de processeurs centraux. La première Xbox (à ne pas confondre avec la Xbox One) utilisait un processeur x86. Dans la deuxième Xbox, c'est-à-dire, désolé, la Xbox 360 utilisait un processeur PowerPC. La troisième Xbox, c'est-à-dire la Xbox One , utilisait le processeur x86 / x64. De tels sauts entre les différents ISA n'ont pas simplifié nos vies.

J'ai participé au travail de l'équipe qui a appris à la Xbox 360 à émuler de nombreux jeux de la première Xbox, c'est-à-dire à émuler x86 sur PowerPC, et pour ce travail, j'ai reçu le titre d ' «émulation ninja» . Ensuite, on m'a demandé d'étudier la question de l'émulation du processeur Xbox 360 PowerPC sur un processeur x64. Je dirai à l'avance que je n'ai pas trouvé de solution satisfaisante.


FMA! = MMA


L'une des choses qui m'a dérangé a été la fusion d'ajouts multiples ou les instructions FMA . Ces instructions ont reçu trois paramètres en entrée, multiplié les deux premiers, puis ajouté le troisième. Fusionné signifie que l'arrondi n'a pas été effectué avant la fin de l'opération. C'est-à-dire que la multiplication est effectuée avec une précision totale, après quoi l'addition est effectuée, et seulement alors le résultat est arrondi à la réponse finale.

Pour montrer cela avec un exemple concret, imaginons que nous utilisons des nombres décimaux à virgule flottante et deux chiffres de précision. Imaginez ce calcul, présenté en fonction:

FMA(8.1e1, 2.9e1, 4.1e1), 8.1e1 * 2.9e1 + 4.1e1, 81 * 29 + 41

81*29 est égal à 2349 et après avoir ajouté 41, nous obtenons 2390 . En arrondissant à deux chiffres, nous obtenons 2400 ou 2.4e3 .

Si nous n'avons pas de FMA, nous devrons d'abord effectuer la multiplication, obtenir 2349 , qui arrondira à deux chiffres de précision et donnera 2300 (2.3e3) . Ensuite, nous ajoutons 41 et nous obtenons 2341 , qui seront à nouveau arrondis et nous obtiendrons le résultat final 2300 (2.3e3) , qui est moins précis que la réponse FMA.

Remarque 1: FMA(a,b, -a*b) calcule l'erreur dans a*b , qui est en fait cool.

Remarque 2: L'un des effets secondaires de la note 1 est que x = a * b – a * b peut ne pas retourner zéro si l'ordinateur génère automatiquement des instructions FMA.

Donc, évidemment, FMA donne des résultats plus précis que les instructions de multiplication et d'addition individuelles. Nous n'irons pas en profondeur, mais nous conviendrons que si nous devons multiplier deux nombres, puis ajouter le troisième, le FMA sera plus précis que ses alternatives. De plus, les instructions FMA ont souvent moins de latence que l'instruction de multiplication suivie de l'instruction d'addition. Dans le processeur Xbox 360, la latence et la vitesse de traitement FMA étaient égales à celles de fmul ou fadd , donc utiliser FMA au lieu de fmul suivi de fadd dépendant a permis de réduire de moitié le retard.

Émulation FMA


Le compilateur Xbox 360 a toujours généré des instructions FMA , vectorielles et scalaires. Nous n'étions pas sûrs que les processeurs x64 que nous avons sélectionnés prendraient en charge ces instructions, il était donc essentiel de les émuler rapidement et avec précision. Il était nécessaire que notre émulation de ces instructions devienne idéale, car d'après mon expérience précédente d'émulation de calculs à virgule flottante, je savais que des résultats "assez proches" entraînaient la chute de personnages à travers le plancher, des voitures volant hors du monde, etc.

Alors, que faut-il pour émuler parfaitement les instructions FMA si le processeur x64 ne les prend pas en charge?

Heureusement, la grande majorité des calculs en virgule flottante dans les jeux sont effectués avec une précision flottante (32 bits), et je pourrais utiliser avec plaisir des instructions avec une double précision (64 bits) dans l'émulation FMA.

Il semble que l'émulation d'instructions FMA avec une précision flottante à l'aide de calculs avec une double précision devrait être simple ( voix du narrateur: mais ce n'est pas le cas; les opérations en virgule flottante ne sont jamais simples ). Float a une précision de 24 bits et double a une précision de 53 bits. Cela signifie que si vous convertissez le flottant entrant en précision double (conversion sans perte), vous pouvez effectuer la multiplication sans erreur. Autrement dit, pour stocker des résultats complètement précis, seulement 48 bits de précision suffisent, et nous en avons plus, c'est-à-dire que tout est en ordre.

Ensuite, nous devons faire l'ajout. Il suffit de prendre le deuxième terme au format flottant, de le convertir en double, puis de l'ajouter au résultat de la multiplication. Étant donné que l'arrondi ne se produit pas dans le processus de multiplication et qu'il n'est effectué qu'après l'addition, cela suffit complètement pour émuler le FMA. Notre logique est parfaite. Vous pouvez déclarer la victoire et rentrer chez vous.

La victoire était si proche ...


Mais ça ne marche pas. Ou du moins, il échoue pour certaines des données entrantes. Réfléchissez à pourquoi cela peut arriver.

Appeler les sons de la musique en attente ...

L'échec se produit car, selon la définition de FMA, la multiplication et l'addition sont effectuées avec une précision totale, après quoi le résultat est arrondi avec un flotteur de précision. Nous avons presque réussi à y parvenir.

La multiplication se produit sans arrondi, puis, après l'ajout, l'arrondi est effectué. C'est semblable à ce que nous essayons de faire. Mais l'arrondi après addition se fait avec une double précision. Après cela, nous devons enregistrer le résultat avec une précision flottante, c'est pourquoi l'arrondi se produit à nouveau.

Pooh Double arrondi .

Il sera difficile de le démontrer clairement, alors revenons à nos formats décimaux à virgule flottante, où la précision simple est de deux décimales et la double précision est de quatre chiffres. Et imaginons que nous calculons FMA(8.1e1, 2.9e1, 9.9e-1) , ou 81 * 29 + .99 .

La réponse exacte à cette expression serait 2349.99 ou 2.34999e3 . Arrondi à la précision simple (deux chiffres), nous obtenons 2.3e3 . Voyons ce qui ne va pas lorsque nous essayons d'émuler ces calculs.

Lorsque nous multiplions 81 et 29 avec une précision du double, nous obtenons 2349 . Jusqu'ici tout va bien.

Ensuite, nous ajoutons .99 et obtenons 2349.99 . Tout va bien.

Ce résultat est arrondi à la précision du double et nous obtenons 2350 (2.350e3) . Oups

Nous l'arrondissons à la précision simple et selon les règles d' arrondi IEEE au plus proche, nous obtenons 2400 (2.4e3) . Ce n'est pas la bonne réponse. Il a une erreur légèrement plus grande que le résultat correctement arrondi renvoyé par l'instruction FMA.

Vous pouvez indiquer que le problème se trouve dans la règle d'environnement IEEE jusqu'au pair le plus proche. Cependant, quelle que soit la règle d'arrondi que vous choisissez, il y aura toujours un cas où un double arrondi renvoie un résultat différent du vrai FMA.

Comment tout cela s'est-il terminé?


Je n'ai pas pu trouver de solution totalement satisfaisante à ce problème.

J'ai quitté l'équipe Xbox bien avant la sortie de la Xbox One, et depuis lors je n'ai pas prêté beaucoup d'attention à la console, donc je ne sais pas quelle décision ils ont prise. Les processeurs x64 modernes ont des instructions FMA qui peuvent parfaitement émuler de telles opérations. Vous pouvez également utiliser le coprocesseur mathématique x87 pour émuler FMA - je ne me souviens pas à quelle conclusion je suis arrivé quand j'ai étudié cette question. Ou peut-être que les développeurs ont simplement décidé que les résultats sont assez proches et peuvent être utilisés.

Source: https://habr.com/ru/post/fr447680/


All Articles