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.