Exercícios de emulação: manual do Xbox 360 FMA


Muitos anos atrás, trabalhei no departamento Microsoft Xbox 360. Pensamos em lançar um novo console e decidimos que seria ótimo se esse console pudesse rodar jogos do console da geração anterior.

A emulação é sempre difícil, mas é ainda mais difícil se seus chefes corporativos estiverem constantemente mudando os tipos de processadores centrais. O primeiro Xbox (que não deve ser confundido com o Xbox One) usava uma CPU x86. No segundo Xbox, desculpe, o Xbox 360 usava um processador PowerPC. O terceiro Xbox, ou seja, o Xbox One , usava a CPU x86 / x64. Tais saltos entre diferentes ISAs não simplificaram nossas vidas.

Participei do trabalho da equipe que ensinou o Xbox 360 a emular muitos jogos do primeiro Xbox, ou seja, emular x86 no PowerPC, e para este trabalho recebi o título de “emulação ninja” . Fui convidado a estudar a questão da emulação da CPU Xbox 360 PowerPC na CPU x64. Direi antecipadamente que não encontrei uma solução satisfatória.


FMA! = MMA


Uma das coisas que me incomodou foi o multiply add fundido, ou instruções FMA . Essas instruções receberam três parâmetros na entrada, multiplicaram os dois primeiros e adicionaram o terceiro. Fundido significava que o arredondamento não era realizado até o final da operação. Ou seja, a multiplicação é realizada com precisão total, após a qual a adição é realizada e somente então o resultado é arredondado para a resposta final.

Para mostrar isso com um exemplo concreto, vamos imaginar que usamos números decimais de ponto flutuante e dois dígitos de precisão. Imagine este cálculo, mostrado como uma função:

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

81*29 é igual a 2349 e, depois de adicionar 41, obtemos 2390 . Arredondando até dois dígitos, obtemos 2400 ou 2.4e3 .

Se não tivermos FMA, primeiro teremos que realizar a multiplicação, obter 2349 , que arredondará até dois bits de precisão e fornecerá 2300 (2.3e3) . Em seguida, adicionamos 41 e obtemos 2341 , que serão arredondados novamente e obteremos o resultado final 2300 (2.3e3) , que é menos preciso do que a resposta das FMA.

Nota 1: FMA(a,b, -a*b) calcula o erro em a*b , o que é realmente interessante.

Nota 2: Um dos efeitos colaterais da Nota 1 é que x = a * b – a * b pode não retornar zero se o computador gerar automaticamente instruções FMA.

Portanto, obviamente, a FMA fornece resultados mais precisos do que as instruções individuais de multiplicação e adição. Não nos aprofundaremos, mas concordaremos que, se precisarmos multiplicar dois números e depois adicionar o terceiro, as FMA serão mais precisas do que suas alternativas. Além disso, as instruções FMA geralmente têm menos latência do que a instrução de multiplicação seguida pela instrução de adição. Na CPU do Xbox 360, a latência e a velocidade de processamento do FMA eram iguais às do fmul ou fadd ; portanto, usar o FMA em vez de fmul seguido pelo fadd dependente permitiu reduzir o atraso pela metade.

Emulação FMA


O compilador do Xbox 360 sempre gerou instruções de FMA , vetoriais e escalares. Não tínhamos certeza de que os processadores x64 que selecionamos suportariam essas instruções, por isso era fundamental imitá-las com rapidez e precisão. Era necessário que nossa emulação dessas instruções se tornasse ideal, porque da minha experiência anterior emular cálculos de ponto flutuante, eu sabia que resultados "razoavelmente próximos" resultavam em caracteres caindo pelo chão, carros voando para fora do mundo e assim por diante.

Então, o que é necessário para emular perfeitamente as instruções FMA se a CPU x64 não as suportar?

Felizmente, a grande maioria dos cálculos de ponto flutuante nos jogos é realizada com precisão de flutuação (32 bits), e eu poderia felizmente usar instruções com precisão dupla (64 bits) na emulação de FMA.

Parece que emular instruções FMA com precisão de flutuação usando cálculos com precisão dupla deve ser simples ( voz do narrador: mas não é; operações de ponto flutuante nunca são simples ). O flutuador tem uma precisão de 24 bits e o dobro tem uma precisão de 53 bits. Isso significa que, se você converter a flutuação de entrada em precisão dupla (conversão sem perdas), poderá executar a multiplicação sem erros. Ou seja, para armazenar resultados completamente precisos, apenas 48 bits de precisão são suficientes e temos mais, isto é, tudo está em ordem.

Então precisamos fazer a adição. Basta pegar o segundo termo no formato flutuante, convertê-lo para o dobro e adicioná-lo ao resultado da multiplicação. Como o arredondamento não ocorre no processo de multiplicação e é realizado somente após a adição, isso é suficiente para emular FMA. Nossa lógica é perfeita. Você pode declarar vitória e voltar para casa.

A vitória estava tão perto ...


Mas isso não funciona. Ou, pelo menos, falha em alguns dos dados recebidos. Reflita sobre por que isso pode acontecer.

A chamada retém sons de música ...

A falha ocorre porque, pela definição de FMA, a multiplicação e a adição são realizadas com precisão total, após o que o resultado é arredondado com um flutuador de precisão. Nós quase conseguimos isso.

A multiplicação ocorre sem arredondamento e, depois da adição, o arredondamento é realizado. Isso é semelhante ao que estamos tentando fazer. Mas o arredondamento após a adição é feito com precisão dupla . Depois disso, precisamos salvar o resultado com precisão de flutuação, e é por isso que o arredondamento ocorre novamente.

Pooh Arredondamento duplo .

Será difícil mostrar isso claramente, então vamos voltar aos nossos formatos decimais de ponto flutuante, onde a precisão única é de duas casas decimais e a precisão dupla de quatro dígitos. E vamos imaginar que calculamos as FMA(8.1e1, 2.9e1, 9.9e-1) ou 81 * 29 + .99 .

A resposta exata para essa expressão seria 2349.99 ou 2.34999e3 . Arredondando para precisão simples (dois dígitos), obtemos 2.3e3 . Vamos ver o que está errado quando tentamos emular esses cálculos.

Quando multiplicamos 81 e 29 com uma precisão de dobro, obtemos 2349 . Até agora tudo bem.

Em seguida, adicionamos .99 e obtemos 2349.99 . Ainda está tudo bem.

Este resultado é arredondado para a precisão do dobro e obtemos 2350 (2.350e3) . Opa

Arredondamos para a precisão simples e de acordo com as regras de arredondamento da IEEE para a mais próxima, mesmo com 2400 (2.4e3) . Esta é a resposta errada. Ele tem um erro um pouco maior que o resultado arredondado corretamente retornado pela instrução FMA.

Você pode indicar que o problema está na regra do ambiente IEEE até a mais próxima. No entanto, independentemente da regra de arredondamento escolhida, sempre haverá um caso em que o arredondamento duplo retorna um resultado diferente do verdadeiro FMA.

Como tudo terminou?


Não consegui encontrar uma solução completamente satisfatória para esse problema.

Deixei a equipe do Xbox muito antes do lançamento do Xbox One e, desde então, não presto muita atenção ao console, então não sei qual decisão eles tomaram. As CPUs modernas x64 possuem instruções FMA que podem emular perfeitamente essas operações. Você também pode usar o coprocessador matemático x87 para emular FMA - não me lembro a que conclusão cheguei quando estudei essa pergunta. Ou talvez os desenvolvedores tenham simplesmente decidido que os resultados são razoavelmente próximos e podem ser usados.

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


All Articles