Compiler C # itu sendiri tidak banyak mengubah IL yang dipancarkan dalam rilis build. Yang perlu dicatat adalah bahwa itu tidak lagi memancarkan opcode NOP yang memungkinkan Anda untuk mengatur breakpoint pada kurung kurawal. Yang besar adalah pengoptimal yang dibangun ke dalam kompiler JIT. Saya tahu itu membuat optimasi berikut:
Metode inlining. Panggilan metode diganti dengan menyuntikkan kode metode. Ini yang besar, itu membuat aksesor properti pada dasarnya gratis.
Alokasi register CPU. Variabel lokal dan argumen metode dapat tetap disimpan dalam register CPU tanpa pernah (atau kurang sering) disimpan kembali ke frame stack. Ini adalah yang besar, penting untuk menyulitkan debugging kode yang dioptimalkan. Dan memberi arti pada kata kunci yang mudah menguap .
Array eliminasi indeks memeriksa. Optimasi penting ketika bekerja dengan array (semua kelas koleksi .NET menggunakan array secara internal). Ketika kompiler JIT dapat memverifikasi bahwa loop tidak pernah mengindeks array dari batas maka itu akan menghilangkan pemeriksaan indeks. Yang besar.
Ulangi membuka gulungan. Loop dengan tubuh kecil ditingkatkan dengan mengulangi kode hingga 4 kali dalam tubuh dan looping lebih sedikit. Mengurangi biaya cabang dan meningkatkan opsi eksekusi skalar super-prosesor.
Penghapusan kode mati. Pernyataan seperti jika (false) {/ ... /} akan sepenuhnya dihilangkan. Ini dapat terjadi karena pelipatan dan pelurusan yang konstan. Kasus lain adalah ketika kompiler JIT dapat menentukan bahwa kode tidak memiliki efek samping yang mungkin. Optimalisasi inilah yang membuat kode profil sangat rumit.
Mengangkat kode. Kode di dalam loop yang tidak terpengaruh oleh loop dapat dipindahkan keluar dari loop. Pengoptimal dari kompiler C akan menghabiskan lebih banyak waktu untuk mencari peluang untuk diangkat. Namun ini adalah optimasi mahal karena analisis aliran data yang diperlukan dan jitter tidak mampu membayar waktu sehingga hanya mengangkat kasus yang jelas. Memaksa .NET programmer untuk menulis kode sumber yang lebih baik dan mengangkat sendiri.
Penghapusan sub-ekspresi umum. x = y + 4; z = y + 4; menjadi z = x; Cukup umum dalam pernyataan seperti dest [ix + 1] = src [ix + 1]; ditulis untuk keterbacaan tanpa memperkenalkan variabel pembantu. Tidak perlu kompromi keterbacaan.
Lipat konstan. x = 1 + 2; menjadi x = 3; Contoh sederhana ini ditangkap lebih awal oleh kompiler, tetapi terjadi pada waktu JIT ketika optimasi lain memungkinkan ini.
Salin propagasi. x = a; y = x; menjadi y = a; Ini membantu pengalokasi register membuat keputusan yang lebih baik. Ini adalah masalah besar dalam x86 jitter karena memiliki beberapa register yang dapat digunakan. Setelah memilih yang tepat sangat penting untuk kinerja.
Ini adalah optimasi yang sangat penting yang dapat membuat besar kesepakatan perbedaan ketika, misalnya, Anda profil Debug membangun aplikasi Anda dan bandingkan dengan Release membangun. Itu hanya benar-benar penting meskipun ketika kode berada di jalur kritis Anda, 5 hingga 10% dari kode yang Anda tulis yang benar - benar mempengaruhi kinerja program Anda. Pengoptimal JIT tidak cukup pintar untuk mengetahui apa yang penting di muka, itu hanya dapat menerapkan tombol "putar ke sebelas" untuk semua kode.
Hasil efektif dari optimasi ini pada waktu eksekusi program Anda sering dipengaruhi oleh kode yang berjalan di tempat lain. Membaca file, mengeksekusi query dbase, dll. Membuat pekerjaan pengoptimal JIT benar-benar tidak terlihat. Itu tidak masalah :)
Pengoptimal JIT adalah kode yang cukup andal, sebagian besar karena telah diuji jutaan kali. Sangat jarang memiliki masalah dalam versi rilis program Anda. Namun itu terjadi. Kegelisahan x64 dan x86 memiliki masalah dengan struct. Jitter x86 memiliki masalah dengan konsistensi titik mengambang, menghasilkan hasil yang sedikit berbeda ketika perantara perhitungan titik mengambang disimpan dalam register FPU pada presisi 80-bit alih-alih terpotong saat disiram ke memori.