Teman saya Aras
baru-baru ini menulis pelacak sinar yang sama dalam berbagai bahasa, termasuk C ++, C #, dan kompiler Unity Burst. Tentu saja, wajar jika mengharapkan bahwa C # akan lebih lambat daripada C ++, tetapi tampaknya menarik bagi saya bahwa Mono lebih lambat dari .NET Core.
Indikator yang dipublikasikannya buruk:
- C # (.NET Core): Mac 17.5 Mray / s,
- C # (Unity, Mono): Mac 4.6 Mray / s,
- C # (Unity, IL2CPP): Mac 17.1 Mray / s
Saya memutuskan untuk melihat apa yang terjadi dan mendokumentasikan tempat-tempat yang dapat ditingkatkan.
Sebagai hasil dari tolok ukur ini dan mempelajari masalah ini, kami menemukan tiga bidang yang memungkinkan peningkatan:
- Pertama, Anda perlu meningkatkan pengaturan Mono default, karena pengguna biasanya tidak mengonfigurasi pengaturan mereka
- Kedua, kita perlu secara aktif memperkenalkan dunia kepada backend optimasi kode LLVM di Mono
- Ketiga, kami meningkatkan penyetelan beberapa parameter Mono.
Titik referensi dari tes ini adalah hasil dari
pelacak ray yang dijalankan pada mesin saya, dan karena saya memiliki perangkat keras yang berbeda, kami tidak dapat membandingkan jumlahnya.
Hasil di rumah saya iMac untuk Mono dan .NET Core adalah sebagai berikut:
Lingkungan kerja | Hasil, MRay / dtk |
---|
.NET Core 2.1.4, build build dotnet run debug | 3.6 |
.NET Core 2.1.4 rilis build dotnet run -c Release | 21.7 |
Vanilla Mono, mono Maths.exe | 6.6 |
Vanilla Mono dengan LLVM dan float32 | 15.5 |
Dalam proses mempelajari masalah ini, kami menemukan beberapa masalah, setelah koreksi di mana hasil berikut diperoleh:
Lingkungan kerja | Hasil, MRay / dtk |
---|
Mono dengan LLVM dan float32 | 15.5 |
Advanced Mono dengan LLVM, float32, dan fixed inline | 29.6 |
Gambaran besar:
Hanya dengan menerapkan LLVM dan float32, Anda dapat meningkatkan kinerja kode floating point hampir 2,3 kali. Dan setelah penyetelan, yang kami tambahkan ke Mono sebagai hasil dari percobaan ini, Anda dapat meningkatkan produktivitas sebesar 4,4 kali dibandingkan dengan Mono standar - parameter ini dalam versi Mono yang akan datang akan menjadi parameter default.
Pada artikel ini saya akan menjelaskan temuan kami.
Float 32-bit dan 64-bit
Aras menggunakan angka floating-point 32-bit untuk bagian utama perhitungan (ketik
float
di C # atau
System.Single
in .NET). Di Mono, kami membuat kesalahan sejak lama - semua perhitungan floating point 32-bit dilakukan sebagai 64-bit, dan data masih disimpan di area 32-bit.
Hari ini, ingatan saya tidak setajam sebelumnya, dan saya tidak ingat persis mengapa kami membuat keputusan seperti itu.
Saya hanya dapat berasumsi bahwa itu dipengaruhi oleh tren dan ide saat itu.
Kemudian aura positif melayang di sekitar komputasi float dengan peningkatan akurasi. Sebagai contoh, prosesor Intel x87 menggunakan presisi 80-bit untuk perhitungan floating point, bahkan ketika operan ganda, yang memberikan pengguna dengan hasil yang lebih akurat.
Pada saat itu, gagasan itu juga relevan bahwa di salah satu proyek saya sebelumnya - spreadsheet Gnumerik - fungsi statistik diimplementasikan lebih efisien daripada di Excel. Oleh karena itu, banyak komunitas sangat menyadari gagasan bahwa hasil yang lebih akurat dengan peningkatan akurasi dapat digunakan.
Pada tahap awal pengembangan Mono, sebagian besar operasi matematika yang dilakukan pada semua platform hanya dapat menerima dua kali lipat pada input. Versi 32-bit ditambahkan ke C99, Posix dan ISO, tetapi pada masa itu tidak tersedia secara luas untuk seluruh industri (misalnya,
sinf
adalah versi float dari
sin
,
fabsf
adalah versi
fabs
, dan sebagainya).
Singkatnya, awal 2000-an adalah masa optimisme.
Aplikasi membayar mahal untuk meningkatkan waktu komputasi, tetapi Mono terutama digunakan untuk aplikasi Linux desktop yang melayani halaman HTTP dan beberapa proses server, jadi kecepatan floating-point bukanlah masalah yang kami temui setiap hari. Itu menjadi nyata hanya di beberapa tolok ukur ilmiah, dan pada tahun 2003 mereka jarang dikembangkan di .NET.
Saat ini, game, aplikasi 3D, pemrosesan gambar, VR, AR, dan pembelajaran mesin telah menjadikan operasi floating point sebagai tipe data yang lebih umum. Masalahnya tidak datang sendiri, dan tidak ada pengecualian. Float bukan lagi tipe data ramah yang digunakan dalam kode hanya di beberapa tempat. Mereka berubah menjadi longsoran salju, dari mana tidak ada tempat untuk bersembunyi. Ada banyak dari mereka dan penyebarannya tidak dapat dihentikan.
Bendera ruang kerja float32
Oleh karena itu, beberapa tahun yang lalu kami memutuskan untuk menambahkan dukungan untuk melakukan operasi float 32-bit menggunakan operasi 32-bit, seperti dalam semua kasus lainnya. Kami menyebut fitur ruang kerja ini "float32". Di Mono, ini diaktifkan dengan menambahkan opsi
--O=float32
di lingkungan kerja, dan dalam aplikasi Xamarin, parameter ini diubah dalam pengaturan proyek.
Bendera baru ini diterima dengan baik oleh pengguna seluler kami, karena pada dasarnya perangkat seluler masih belum terlalu kuat, dan mereka lebih baik memproses data lebih cepat daripada meningkatkan akurasi. Kami menyarankan agar pengguna seluler mengaktifkan compiler optimalisasi LLVM dan bendera float32 secara bersamaan.
Meskipun bendera ini telah diterapkan selama beberapa tahun, kami tidak menjadikannya bawaan untuk menghindari kejutan yang tidak menyenangkan bagi pengguna. Namun, kami mulai menghadapi kasus-kasus di mana kejutan muncul karena perilaku 64-bit standar, lihat
laporan bug ini
diajukan oleh pengguna Unity .
Sekarang kita akan menggunakan Mono
float32
, kemajuan dapat dilacak di sini:
https://github.com/mono/mono/issues/6985 .
Sementara itu, saya kembali ke proyek teman saya Aras. Dia menggunakan API baru yang ditambahkan ke .NET Core. Meskipun .NET Core selalu melakukan operasi float 32-bit sebagai float 32-bit, API
System.Math
masih melakukan konversi dari
float
menjadi
double
dalam proses. Misalnya, jika Anda perlu menghitung fungsi sinus untuk nilai float, maka satu-satunya pilihan adalah memanggil
Math.Sin (double)
, dan Anda harus mengonversi dari float ke double.
Untuk memperbaikinya, tipe baru
System.MathF
ditambahkan ke .NET Core, yang berisi operasi matematika floating-point presisi tunggal, dan sekarang kami hanya mem-porting
[System.MathF]
ke Mono .
Transisi dari float 64-bit ke 32-bit secara signifikan meningkatkan kinerja, yang dapat dilihat dari tabel ini:
Lingkungan kerja dan opsi | Tuan / detik |
---|
Mono dengan System.Math | 6.6 |
Mono dengan System.Math dan -O=float32 | 8.1 |
Mono dengan System.MathF | 6.5 |
Mono dengan System.MathF dan -O=float32 | 8.2 |
Artinya, menggunakan
float32
dalam tes ini benar-benar meningkatkan kinerja, dan MathF tidak banyak berpengaruh.
Pengaturan LLVM
Dalam proses penelitian ini, kami menemukan bahwa meskipun kompiler Fast JIT Mono memiliki dukungan
float32
, kami tidak menambahkan dukungan ini ke backend LLVM. Ini berarti bahwa Mono dengan LLVM masih melakukan konversi mahal dari float menjadi double.
Oleh karena itu, Zoltan menambahkan dukungan
float32
ke mesin pembuatan kode LLVM.
Kemudian ia memperhatikan bahwa inliner kami menggunakan heuristik yang sama untuk Fast JIT seperti yang digunakan untuk LLVM. Ketika bekerja dengan Fast JIT, perlu untuk menyeimbangkan antara kecepatan JIT dan kecepatan eksekusi, oleh karena itu kami membatasi jumlah kode tertanam untuk mengurangi jumlah pekerjaan mesin JIT.
Tetapi jika Anda memutuskan untuk menggunakan LLVM di Mono, maka Anda berusaha untuk kode secepat mungkin, jadi kami mengubah pengaturannya. Hari ini, parameter ini dapat diubah menggunakan
MONO_INLINELIMIT
lingkungan
MONO_INLINELIMIT
, tetapi sebenarnya perlu ditulis dengan nilai default.
Berikut adalah hasil dengan pengaturan LLVM yang dimodifikasi:
Lingkungan kerja dan opsi | Tuan / detik |
---|
Mono dengan System.Math --llvm -O=float32 | 16.0 |
Mono dengan System.Math --llvm -O=float32 , heuristik konstan | 29.1 |
Mono dengan System.MathF --llvm -O=float32 , heuristik konstan | 29.6 |
Langkah selanjutnya
Dibutuhkan sedikit upaya untuk melakukan semua perbaikan ini. Perubahan ini dipimpin oleh diskusi berkala di Slack. Saya bahkan berhasil membuat beberapa jam suatu malam ke port
System.MathF
ke Mono.
Kode pelacak sinar Aras telah menjadi subjek yang ideal untuk dipelajari karena mandiri, merupakan aplikasi nyata, dan bukan tolok ukur sintetis. Kami ingin menemukan perangkat lunak serupa lainnya yang dapat digunakan untuk mempelajari kode biner yang kami hasilkan, dan memastikan bahwa kami memberikan LLVM data terbaik untuk pelaksanaan pekerjaannya yang optimal.
Kami juga mempertimbangkan untuk memperbarui LLVM kami, dan menggunakan optimasi tambahan yang baru.
Catatan terpisah
Ketelitian ekstra memiliki efek samping yang bagus. Sebagai contoh, membaca permintaan kumpulan mesin Godot, saya melihat bahwa ada diskusi aktif tentang apakah membuat akurasi operasi floating point disesuaikan pada waktu kompilasi (
https://github.com/godotengine/godot/pull/17134 ).
Saya bertanya kepada Juan mengapa ini mungkin perlu bagi seseorang, karena saya percaya bahwa operasi floating-point 32-bit cukup untuk permainan.
Juan menjelaskan bahwa dalam kasus umum, mengapung berfungsi dengan baik, tetapi jika Anda "menjauh" dari pusat, katakanlah, pindah 100 kilometer dari pusat permainan, kesalahan perhitungan mulai menumpuk, yang dapat menyebabkan gangguan grafis yang menarik. Anda dapat menggunakan strategi berbeda untuk mengurangi dampak masalah ini, dan salah satunya adalah bekerja dengan akurasi yang lebih tinggi, yang harus Anda bayar kinerjanya.
Tak lama setelah percakapan kami di feed Twitter saya, saya melihat sebuah pos yang menunjukkan masalah ini:
http://pharr.org/matt/blog/2018/03/02/rendering-in-camera-space.htmlMasalahnya ditunjukkan pada gambar di bawah ini. Di sini kita melihat model mobil sport dari paket pbrt-v3-scenes **
. Baik kamera dan pemandangannya dekat asal, dan semuanya tampak hebat.**
(Penulis Yasutoshi Mori .)Kemudian kami memindahkan kamera dan tempat kejadian 200.000 unit di xx, yy dan zz dari asal. Dapat dilihat bahwa model mesin telah menjadi sangat terfragmentasi; ini semata-mata karena kurangnya presisi dalam angka floating point.
Jika kita bergerak lebih jauh 5x5x5 kali, 1 juta unit dari asalnya, modelnya mulai hancur; mesin berubah menjadi perkiraan voxel yang sangat kasar, menarik dan menakutkan. (Keanu mengajukan pertanyaan: Apakah Minecraft begitu kubik hanya karena semuanya dirender sangat jauh dari asalnya?)**
(Saya minta maaf kepada Yasutoshi Mori atas apa yang kami lakukan dengan model cantiknya.)