Latihan emulasi: manual Xbox 360 FMA


Beberapa tahun yang lalu, saya bekerja di departemen Microsoft Xbox 360. Kami berpikir untuk merilis konsol baru, dan memutuskan bahwa akan bagus jika konsol ini dapat menjalankan game dari konsol generasi sebelumnya.

Emulasi selalu sulit, tetapi bahkan lebih sulit jika bos perusahaan Anda terus-menerus mengubah jenis prosesor sentral. Xbox pertama (jangan dikelirukan dengan Xbox One) menggunakan CPU x86. Di Xbox kedua, yaitu, maaf, Xbox 360 menggunakan prosesor PowerPC. Xbox ketiga, yaitu, Xbox One , menggunakan CPU x86 / x64. Lompatan seperti itu di antara berbagai ISA tidak menyederhanakan hidup kita.

Saya berpartisipasi dalam pekerjaan tim yang mengajarkan Xbox 360 untuk meniru banyak game Xbox pertama, yaitu, meniru x86 pada PowerPC, dan untuk pekerjaan ini saya menerima gelar "emulasi ninja" . Kemudian saya diminta untuk mempelajari masalah meniru CPU Xbox 360 PowerPC pada CPU x64. Saya akan mengatakan sebelumnya bahwa saya belum menemukan solusi yang memuaskan.


FMA! = MMA


Salah satu hal yang menggangguku adalah penggabungan multiply add, atau instruksi FMA . Instruksi ini menerima tiga parameter pada input, mengalikan dua yang pertama, dan kemudian menambahkan yang ketiga. Fused berarti pembulatan tidak dilakukan sampai akhir operasi. Yaitu, perkalian dilakukan dengan akurasi penuh, setelah itu penambahan dilakukan, dan hanya kemudian hasilnya dibulatkan ke jawaban akhir.

Untuk menunjukkan ini dengan contoh nyata, mari kita bayangkan bahwa kita menggunakan angka floating-point desimal dan dua digit presisi. Bayangkan perhitungan ini, ditampilkan sebagai fungsi:

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

81*29 sama dengan 2349 dan setelah menambahkan 41 kita mendapatkan 2390 . Membulatkan hingga dua digit, kita dapatkan 2400 atau 2.4e3 .

Jika kita tidak memiliki FMA, maka pertama kita harus melakukan perkalian, dapatkan 2349 , yang akan mengumpulkan hingga dua digit akurasi dan memberikan 2300 (2.3e3) . Kemudian kita menambahkan 41 dan kita mendapatkan 2341 , yang akan dibulatkan lagi dan kita akan mendapatkan hasil akhir 2300 (2.3e3) , yang kurang akurat daripada jawaban FMA.

Catatan 1: FMA(a,b, -a*b) menghitung kesalahan dalam a*b , yang sebenarnya keren.

Catatan 2: Salah satu efek samping dari Catatan 1 adalah bahwa x = a * b – a * b mungkin tidak mengembalikan nol jika komputer secara otomatis menghasilkan instruksi FMA.

Jadi, jelas, FMA memberikan hasil yang lebih akurat daripada instruksi perkalian dan penambahan individu. Kami tidak akan masuk terlalu dalam, tetapi kami akan setuju bahwa jika kami perlu mengalikan dua angka dan kemudian menambahkan yang ketiga, maka FMA akan lebih akurat daripada alternatifnya. Selain itu, instruksi FMA sering memiliki latensi kurang dari instruksi perkalian diikuti oleh instruksi penambahan. Di CPU Xbox 360, kecepatan pemrosesan latensi dan FMA sama dengan kecepatan fmul atau fadd , jadi menggunakan FMA alih-alih fmul diikuti dengan ketergantungan fadd memungkinkan untuk mengurangi penundaan hingga setengahnya.

Emulasi FMA


Kompiler Xbox 360 selalu menghasilkan instruksi FMA , baik vektor maupun skalar. Kami tidak yakin bahwa prosesor x64 yang kami pilih akan mendukung instruksi ini, jadi sangat penting untuk meniru mereka dengan cepat dan akurat. Diperlukan agar emulasi kami terhadap instruksi ini menjadi ideal, karena dari pengalaman saya sebelumnya meniru perhitungan floating-point, saya tahu bahwa hasil "cukup dekat" menyebabkan karakter jatuh melalui lantai, mobil terbang keluar dari dunia, dan sebagainya.

Jadi apa yang diperlukan untuk meniru instruksi FMA dengan sempurna jika CPU x64 tidak mendukungnya?

Untungnya, sebagian besar perhitungan floating point dalam game dilakukan dengan presisi float (32 bit), dan saya dengan senang hati dapat menggunakan instruksi dengan presisi ganda (64 bit) dalam emulasi FMA.

Tampaknya meniru instruksi FMA dengan presisi float menggunakan perhitungan dengan presisi ganda harus sederhana ( suara narator: tetapi tidak; operasi floating point tidak pernah sederhana ). Float memiliki akurasi 24 bit, dan double memiliki akurasi 53 bit. Ini berarti bahwa jika Anda mengkonversi float yang masuk ke double presisi (konversi lossless), maka Anda dapat melakukan perkalian tanpa kesalahan. Artinya, untuk menyimpan hasil yang benar-benar akurat, akurasi hanya 48 bit yang cukup, dan kami memiliki lebih banyak, yaitu, semuanya teratur.

Maka kita perlu melakukan penambahan. Cukup dengan mengambil istilah kedua dalam format float, mengubahnya menjadi dua kali lipat, dan kemudian menambahkannya ke hasil perkalian. Karena pembulatan tidak terjadi dalam proses perkalian, dan itu dilakukan hanya setelah penambahan, ini sepenuhnya cukup untuk meniru FMA. Logika kita sempurna. Anda dapat mendeklarasikan kemenangan dan kembali ke rumah.

Kemenangan itu begitu dekat ...


Tapi itu tidak berhasil. Atau setidaknya gagal untuk beberapa data yang masuk. Renungkan sendiri mengapa ini bisa terjadi.

Panggilan tahan suara musik ...

Kegagalan terjadi karena, menurut definisi FMA, perkalian dan penambahan dilakukan dengan presisi penuh, setelah itu hasilnya dibulatkan dengan pelampung presisi. Kami hampir berhasil mencapai ini.

Perkalian terjadi tanpa pembulatan, dan kemudian, setelah penambahan, pembulatan dilakukan. Ini mirip dengan apa yang kami coba lakukan. Namun pembulatan setelah penambahan dilakukan dengan presisi ganda . Setelah itu, kita perlu menyimpan hasilnya dengan presisi float, itulah sebabnya pembulatan terjadi lagi.

Pooh Pembulatan ganda .

Akan sulit untuk menunjukkan ini dengan jelas, jadi mari kita kembali ke format floating-point desimal kami, di mana presisi tunggal adalah dua tempat desimal dan presisi ganda adalah empat digit. Dan mari kita bayangkan bahwa kita menghitung FMA(8.1e1, 2.9e1, 9.9e-1) , atau 81 * 29 + .99 .

Jawaban tepat untuk ungkapan ini adalah 2349.99 atau 2.34999e3 . Membulatkan ke presisi tunggal (dua digit), kita mendapatkan 2.3e3 . Mari kita lihat apa yang salah ketika kita mencoba meniru perhitungan ini.

Ketika kita mengalikan 81 dan 29 dengan akurasi ganda, kita mendapatkan 2349 . Sejauh ini bagus.

Lalu kita tambahkan .99 dan dapatkan 2349.99 . Semuanya masih baik-baik saja.

Hasil ini dibulatkan ke ketepatan ganda dan kami mendapatkan 2350 (2.350e3) . Ups

Kami membulatkannya ke presisi tunggal dan sesuai dengan aturan pembulatan IEEE ke yang terdekat bahkan kami dapatkan 2400 (2.4e3) . Ini jawaban yang salah. Ini memiliki kesalahan sedikit lebih besar daripada hasil dibulatkan dengan benar dikembalikan oleh instruksi FMA.

Anda dapat menyatakan bahwa masalahnya ada di aturan lingkungan IEEE hingga yang terdekat sekalipun. Namun, tidak peduli apa aturan pembulatan yang Anda pilih, akan selalu ada kasus di mana pembulatan ganda menghasilkan hasil yang berbeda dari FMA yang sebenarnya.

Bagaimana semuanya berakhir?


Saya tidak dapat menemukan solusi yang sepenuhnya memuaskan untuk masalah ini.

Saya meninggalkan tim Xbox jauh sebelum rilis Xbox One dan sejak itu saya tidak terlalu memperhatikan konsol, jadi saya tidak tahu keputusan apa yang mereka buat. CPU x64 modern memiliki instruksi FMA yang dapat dengan sempurna meniru operasi tersebut. Anda juga dapat entah bagaimana menggunakan koprosesor matematika x87 untuk meniru FMA - Saya tidak ingat apa kesimpulan saya ketika saya mempelajari pertanyaan ini. Atau mungkin para pengembang hanya memutuskan bahwa hasilnya cukup dekat dan dapat digunakan.

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


All Articles