
Visual Studio 2019 Pratinjau 3 memperkenalkan fitur baru untuk mengurangi ukuran biner penanganan pengecualian C ++ (coba / tangkap dan penghancur otomatis) pada x64. Dijuluki FH4 (untuk __CxxFrameHandler4, lihat di bawah), saya mengembangkan format dan pemrosesan baru untuk data yang digunakan untuk penanganan pengecualian C ++ yang ~ 60% lebih kecil dari implementasi yang ada sehingga menghasilkan pengurangan biner keseluruhan hingga 20% untuk program dengan penggunaan C ++ yang berat penanganan pengecualian.
Artikel ini di
blog .
Bagaimana Saya Menghidupkan Ini?
FH4 saat ini mati secara default karena perubahan runtime yang diperlukan untuk aplikasi Store tidak dapat membuatnya menjadi rilis saat ini. Untuk mengaktifkan FH4 untuk aplikasi non-Toko, berikan flag tidak berdokumen "/ d2FH4" ke kompiler MSVC di Visual Studio 2019 Pratinjau 3 dan seterusnya.
Kami berencana mengaktifkan FH4 secara default setelah runtime Store diperbarui. Kami berharap untuk melakukan ini di Visual Studio 2019 Pembaruan 1 dan akan memperbarui posting ini yang kita tahu lebih banyak.
Perubahan alat
Setiap instalasi Visual Studio 2019 Pratinjau 3 dan seterusnya akan memiliki perubahan dalam kompiler dan runtime C ++ untuk mendukung FH4. Perubahan kompiler ada secara internal di bawah bendera β/ d2FH4β yang disebutkan di atas. C ++ runtime menampilkan DLL baru bernama vcruntime140_1.dll yang diinstal secara otomatis oleh VCRedist. Ini diperlukan untuk mengekspos penangan pengecualian baru __CxxFrameHandler4 yang menggantikan rutin __CxxFrameHandler3 yang lebih lama. Penautan statis dan penyebaran aplikasi-lokal dari runtime C ++ baru juga didukung.
Sekarang ke hal-hal yang menyenangkan! Sisa dari posting ini akan membahas hasil internal dari percobaan FH4 pada Windows, Office, dan SQL, diikuti oleh rincian teknis yang lebih mendalam di balik teknologi baru ini.
Motivasi dan hasil
Sekitar setahun yang lalu, mitra kami pada proyek C ++ / WinR T datang ke tim Microsoft C ++ dengan sebuah tantangan: seberapa besar kita dapat mengurangi ukuran biner penanganan pengecualian C ++ untuk program yang banyak menggunakannya?
Dalam konteks program yang menggunakan C ++ / WinRT , mereka mengarahkan kami ke komponen Windows Microsoft.UI.Xaml.dll yang dikenal memiliki jejak biner besar karena penanganan pengecualian C ++. Saya mengkonfirmasi bahwa ini memang benar dan menghasilkan pemecahan ukuran biner dengan __CxxFrameHandler3 yang ada, ditunjukkan di bawah ini. Persentase di sisi kanan grafik adalah persen dari total ukuran biner yang ditempati oleh tabel metadata tertentu dan kode yang diuraikan.

Saya tidak akan membahas di posting ini apa struktur spesifik di sisi kanan grafik (lihat pembicaraan James McNellis tentang bagaimana stack unwinding bekerja pada Windows untuk lebih jelasnya). Melihat total metadata dan kode, 26,4% kekalahan dari ukuran biner digunakan oleh C ++ exception handling. Ini adalah jumlah ruang yang sangat besar dan menghambat adopsi C ++ / WinRT.
Kami telah membuat perubahan di masa lalu untuk mengurangi ukuran penanganan pengecualian C ++ di kompiler tanpa mengubah runtime. Ini termasuk menjatuhkan metadata untuk wilayah kode yang tidak bisa melempar dan melipat negara yang identik secara logis. Namun, kami mencapai akhir dari apa yang bisa kami lakukan hanya dalam kompiler dan tidak akan dapat membuat penyok signifikan dalam sesuatu sebesar ini. Analisis menunjukkan bahwa ada kemenangan signifikan yang bisa didapat tetapi diperlukan perubahan mendasar pada data, kode, dan runtime. Jadi kami pergi ke depan dan melakukannya.
Dengan __CxxFrameHandler4 baru dan metadata yang menyertainya, rincian ukuran untuk Microsoft.UI.XAML.dll sekarang adalah sebagai berikut:

Ukuran biner yang digunakan oleh penanganan pengecualian C ++ turun 64% yang mengarah ke penurunan ukuran biner keseluruhan sebesar 18,6% pada biner ini. Setiap jenis struktur menyusut dalam ukuran dengan derajat mengejutkan:
Eh data | Ukuran __CxxFrameHandler3 (Bytes) | Ukuran __CxxFrameHandler4 (Bytes) | % Pengurangan Ukuran |
Entri Pdata | 147.864 | 118.260 | 20,0% |
Lepaskan kode | 224.284 | 92.810 | 58,6% |
Informasi fungsi | 255.440 | 27.755 | 89,1% |
Peta IP2State | 186.944 | 45.098 | 75,9% |
Lepaskan peta | 80.952 | 69.757 | 13,8% |
Tangkap peta penangan | 52.060 | 6,147 | 88,2% |
Coba peta | 51.960 | 5,196 | 90,0% |
Dlets funclets | 54.570 | 45.739 | 16,2% |
Tangkap funclets | 102.400 | 4,301 | 95,8% |
Total | 1.156.474 | 415.063 | 64,1% |
Gabungan, beralih ke __CxxFrameHandler4 menjatuhkan ukuran keseluruhan Microsoft.UI.Xaml.dll dari 4,4 MB menjadi 3,6 MB.
Mengujicobakan FH4 pada set representatif binari Office menunjukkan ~ 10% pengurangan ukuran dalam DLL yang menggunakan banyak pengecualian. Bahkan di Word dan Excel, yang dirancang untuk meminimalkan penggunaan pengecualian, masih ada pengurangan yang berarti dalam ukuran biner.
Biner | Ukuran Lama (MB) | Ukuran Baru (MB) | % Pengurangan Ukuran | Deskripsi |
chart.dll | 17.27 | 15.10 | 12,6% | Dukungan untuk berinteraksi dengan grafik dan grafik |
Csi.dll | 9,78 | 8.66 | 11,4% | Dukungan untuk bekerja dengan file yang disimpan di cloud |
Mso20Win32Client.dll | 6.07 | 5.41 | 11,0% | Kode umum yang dibagikan di antara semua aplikasi Office |
Mso30Win32Client.dll | 8.11 | 7.30 | 9,9% | Kode umum yang dibagikan di antara semua aplikasi Office |
oart.dll | 18.21 | 16.20 | 11,0% | Fitur grafik yang dibagikan di antara aplikasi Office |
wwlib.dll | 42.15 | 41.12 | 2,5% | Biner utama Microsoft Word |
excel.exe | 52.86 | 50.29 | 4,9% | Biner utama Microsoft Excel |
Mengujicoba FH4 pada inti binari SQL menunjukkan pengurangan ukuran 4-21%, terutama dari kompresi metadata yang dijelaskan di bagian selanjutnya:
Biner | Ukuran Lama (MB) | Ukuran Baru (MB) | % Pengurangan Ukuran | Deskripsi |
sqllang.dll | 47.12 | 44.33 | 5,9% | Layanan tingkat atas: Pengurai bahasa, pengikat, pengoptimal, dan mesin eksekusi |
sqlmin.dll | 48.17 | 45.83 | 4,8% | Layanan tingkat rendah: mesin transaksi dan penyimpanan |
qds.dll | 1.42 | 1.33 | 6,3% | Fungsionalitas toko kueri |
SqlDK.dll | 3.19 | 3.05 | 4,4% | Abstraksi SQL OS: memori, utas, penjadwalan, dll. |
autoadmin.dll | 1.77 | 1.64 | 7,3% | Penasihat logika penyetelan basis data |
xedetours.dll | 0,45 | 0,36 | 21,6% | Perekam data penerbangan untuk pertanyaan |
Teknologi
Ketika menganalisis apa yang menyebabkan pengecualian C ++ penanganan data menjadi sangat besar di Microsoft.UI.Xaml.dll saya menemukan dua penyebab utama:
- Struktur data sendiri besar: tabel metadata berukuran tetap dengan bidang offset relatif gambar dan bilangan bulat masing-masing berukuran empat byte. Fungsi dengan satu coba / tangkap dan satu atau dua destruktor otomatis memiliki lebih dari 100 byte metadata.
- Struktur data dan kode yang dihasilkan tidak dapat digabungkan. Tabel metadata berisi offset relatif-gambar yang mencegah pelipatan COMDAT (proses di mana penghubung dapat melipat bersama potongan data yang identik untuk menghemat ruang) kecuali fungsi yang diwakilinya identik. Sebagai tambahan, catch funclets (kode yang diuraikan dari blok catch program) tidak dapat dilipat meskipun kode-identik karena metadata mereka terkandung dalam orang tua mereka.
Untuk mengatasi masalah ini, FH4 merestrukturisasi metadata dan kode sedemikian rupa sehingga:
- Nilai-nilai ukuran tetap sebelumnya telah dikompresi menggunakan pengkodean integer panjang variabel yang turun> 90% dari bidang metadata dari empat byte ke satu. Tabel metadata sekarang juga panjang variabel dengan header untuk menunjukkan jika bidang tertentu hadir untuk menghemat ruang memancarkan bidang kosong.
- Semua offset gambar-relatif yang bisa menjadi fungsi-relatif telah dibuat fungsi-relatif. Ini memungkinkan COMDAT melipat antara metadata dari fungsi yang berbeda dengan karakteristik yang sama (pikirkan contoh template) dan memungkinkan nilai-nilai ini dikompresi. Catch funclets telah dirancang ulang untuk tidak lagi menyimpan metadata mereka di orang tua mereka sehingga setiap fungsi catch yang identik dengan kode sekarang dapat dilipat menjadi satu salinan dalam biner.
Untuk mengilustrasikan ini, mari kita lihat definisi asli untuk tabel metadata Function Info yang digunakan untuk __CxxFrameHandler3. Ini adalah tabel awal untuk runtime saat memproses EH dan menunjuk ke tabel metadata lainnya. Kode ini tersedia untuk umum di setiap instalasi VS, cari <VS path install> \ VC \ Tools \ MSVC \ <version> \ termasuk \ ehdata.h:
typedef const struct _s_FuncInfo { unsigned int magicNumber:29;
Struktur ini berukuran tetap berisi 10 bidang masing-masing 4 byte. Ini berarti setiap fungsi yang membutuhkan penanganan pengecualian C ++ secara default menghasilkan 40 byte metadata.
Sekarang ke struktur data baru (<VS install path> \ VC \ Tools \ MSVC \ <version> \ termasuk \ ehdata4_export.h):
struct FuncInfoHeader { union { struct { uint8_t isCatch : 1;
Perhatikan bahwa:
- Angka ajaib telah dihapus, memancarkan 0x19930522 setiap kali menjadi masalah ketika sebuah program memiliki ribuan entri ini.
- EHFlags telah dipindahkan ke header sementara dispESTypeList telah dihapus karena menjatuhkan dukungan spesifikasi pengecualian dinamis di C ++ 17. Compiler akan default ke __CxxFrameHandler3 yang lebih lama jika spesifikasi pengecualian dinamis digunakan.
- Panjang tabel lainnya tidak lagi disimpan dalam "Info Fungsi 4". Hal ini memungkinkan lipatan COMDAT untuk melipat lebih banyak dari tabel menunjuk-ke ββbahkan jika tabel "Info Fungsi 4" itu sendiri tidak dapat dilipat.
- (Tidak diperlihatkan secara eksplisit) Bidang dispFrame dan bbtFlags sekarang adalah bilangan bulat panjang variabel. Representasi tingkat tinggi menjadikannya sebagai uint32_t untuk pemrosesan yang mudah.
- bbtFlags, dispUnwindMap, dispTryBlockMap, dan dispFrame dapat dihilangkan tergantung pada bidang yang diatur dalam header.
Dengan mempertimbangkan semua ini, ukuran rata-rata struktur "Function Info 4" yang baru sekarang adalah 13 byte (header 1 byte + tiga gambar relatif 4 byte offset ke tabel lain) yang dapat menurunkan skala lebih jauh jika beberapa tabel tidak diperlukan. Panjang tabel dipindahkan, tetapi nilai-nilai ini sekarang dikompresi dan 90% di Microsoft.UI.Xaml.dll ditemukan sesuai dalam satu byte. Menyatukan semua itu, ini berarti ukuran rata-rata untuk mewakili data fungsional yang sama di handler baru adalah 16 byte dibandingkan dengan 40 byte sebelumnya - peningkatan yang cukup dramatis!
Untuk melipat, mari kita lihat jumlah meja dan fungsi unik dengan penangan lama dan baru:
Eh data | Hitung di __CxxFrameHandler3 | Hitung di __CxxFrameHandler4 | % Pengurangan |
Entri Pdata | 12.322 | 9,855 | 20,0% |
Informasi fungsi | 6,386 | 2,747 | 57,0% |
Entri Peta IP2State | 6,363 | 2,148 | 66,2% |
Lepaskan entri peta | 1,487 | 1,464 | 1,5% |
Tangkap peta penangan | 2,603 | 601 | 76,9% |
Coba peta | 2.598 | 648 | 75,1% |
Dlets funclets | 2,301 | 1,527 | 33,6% |
Tangkap funclets | 2,603 | 84 | 96,8% |
Total | 36.663 | 19.074 | 48.0% |
Jumlah entri data EH unik turun 48% dari menciptakan peluang lipat tambahan dengan menghapus RVA dan mendesain ulang funclets tangkapan. Saya secara khusus ingin menyebutkan jumlah fungsi tangkapan yang dicetak miring dalam warna hijau: turun dari 2.603 menjadi hanya 84. Ini adalah konsekuensi dari C ++ / WinRT yang menerjemahkan HRESULTs ke pengecualian C ++ yang menghasilkan banyak fungsi tangkapan yang identik dengan kode yang sekarang dapat terlipat. Tentu saja penurunan sebesar ini ada pada hasil akhir yang tinggi, namun demikian menunjukkan potensi penghematan ukuran yang dapat dicapai ketika struktur data dirancang dengan mempertimbangkan hal itu.
Performa
Dengan desain yang memperkenalkan kompresi dan memodifikasi eksekusi runtime, ada kekhawatiran kinerja penanganan pengecualian terpengaruh. Namun, dampaknya adalah positif : kinerja penanganan pengecualian meningkat dengan __CxxFrameHandler4 dibandingkan dengan __CxxFrameHandler3. Saya menguji throughput menggunakan program benchmark yang melepaskan 100 frame stack masing-masing dengan try / catch dan 3 objek otomatis untuk dihancurkan. Ini dijalankan 50.000 kali untuk waktu eksekusi profil, yang mengarah ke keseluruhan waktu eksekusi:
| __CxxFrameHandler3 | __CxxFrameHandler4 |
Waktu eksekusi | 4.84s | 4.25-an |
Profiling menunjukkan bahwa dekompresi memang memperkenalkan waktu pemrosesan tambahan tetapi biayanya lebih besar daripada toko yang lebih sedikit untuk penyimpanan thread-lokal dalam desain runtime baru.
Rencana masa depan
Seperti disebutkan dalam judul, FH4 saat ini hanya diaktifkan untuk binari x64. Namun, teknik yang dijelaskan dapat diperluas untuk ARM32 / ARM64 dan pada tingkat lebih rendah x86. Kami sedang mencari contoh yang baik (seperti Microsoft.UI.Xaml.dll) untuk memotivasi perluasan teknologi ini ke platform lain - jika Anda pikir Anda memiliki kasus penggunaan yang baik, beri tahu kami!
Proses mengintegrasikan perubahan runtime untuk aplikasi Store untuk mendukung FH4 sedang dalam proses. Setelah selesai, penangan baru akan diaktifkan secara default sehingga semua orang bisa mendapatkan penghematan ukuran biner ini tanpa usaha tambahan.
Komentar penutup
Bagi siapa pun yang berpikir biner x64 mereka dapat melakukannya dengan beberapa pengurangan: coba FH4 (via '/ d2FH4') hari ini! Kami senang melihat penghematan apa yang dapat diberikan ini sekarang setelah fitur ini keluar dari alam. Tentu saja, jika Anda mengalami masalah apa pun, beri tahu kami di komentar di bawah ini, melalui email ( visualcpp@microsoft.com ), atau melalui Komunitas Pengembang . Anda juga dapat menemukan kami di Twitter ( @VisualC ).
Terima kasih kepada Kenny Kerr karena mengarahkan kami ke Microsoft.UI.Xaml.dll, Ravi Pinjala untuk mengumpulkan angka-angka di Office, dan Robert Roessler untuk menguji coba ini pada SQL.