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.