Pengoptimuman prestasi JVM, Bahagian 2: Penyusun

Penyusun Java menjadi pentas utama dalam artikel kedua ini dalam siri pengoptimuman prestasi JVM. Eva Andreasson memperkenalkan kumpulan penyusun yang berbeza dan membandingkan hasil prestasi dari kompilasi pelanggan, pelayan dan berjenjang. Dia menyimpulkan dengan gambaran umum pengoptimuman JVM biasa seperti penghapusan kod mati, inlining, dan pengoptimuman gelung.

Penyusun Java adalah sumber kebebasan platform yang terkenal di Jawa. Seorang pembangun perisian menulis aplikasi Java terbaik yang dia dapat, dan kemudian penyusunnya bekerja di belakang layar untuk menghasilkan kod pelaksanaan yang cekap dan berkinerja baik untuk platform sasaran yang dimaksudkan. Pelbagai jenis penyusun memenuhi pelbagai keperluan aplikasi, sehingga menghasilkan hasil prestasi tertentu yang diinginkan. Semakin banyak yang anda fahami mengenai penyusun, dari segi cara kerjanya dan jenis apa yang ada, anda akan dapat mengoptimumkan prestasi aplikasi Java.

Artikel kedua ini dalam siri pengoptimuman prestasi JVM menyoroti dan menjelaskan perbezaan antara pelbagai penyusun mesin maya Java. Saya juga akan membincangkan beberapa pengoptimuman umum yang digunakan oleh penyusun Just-In-Time (JIT) untuk Java. (Lihat "Pengoptimuman prestasi JVM, Bahagian 1" untuk gambaran keseluruhan JVM dan pengenalan siri ini.)

Apa itu penyusun?

Cukup sebut penyusun mengambil bahasa pengaturcaraan sebagai input dan menghasilkan bahasa yang dapat dilaksanakan sebagai output. Salah satu penyusun yang biasa diketahui adalah javac, yang disertakan dalam semua kit pengembangan Java standard (JDK). javacmengambil kod Java sebagai input dan menerjemahkannya ke dalam kod bytec - bahasa yang dapat dilaksanakan untuk JVM. Bytecode disimpan ke dalam file .class yang dimuat ke dalam runtime Java ketika proses Java dimulakan.

Bytecode tidak dapat dibaca oleh CPU standard dan perlu diterjemahkan ke dalam bahasa arahan yang dapat difahami oleh platform pelaksanaan yang mendasari. Komponen dalam JVM yang bertanggungjawab untuk menerjemahkan bytecode ke arahan platform yang boleh dilaksanakan masih merupakan penyusun lain. Beberapa penyusun JVM mengendalikan beberapa peringkat terjemahan; sebagai contoh, penyusun mungkin membuat pelbagai tahap perwakilan kod bytase pertengahan sebelum berubah menjadi petunjuk mesin yang sebenarnya, langkah terakhir terjemahan.

Bytecode dan JVM

Sekiranya anda ingin mengetahui lebih lanjut mengenai bytecode dan JVM, lihat "Asas Bytecode" (Bill Venners, JavaWorld).

Dari perspektif platform-agnostik, kami ingin memastikan platform kod tidak bebas sejauh mungkin, sehingga tahap terjemahan terakhir - dari perwakilan terendah hingga kod mesin sebenar - adalah langkah yang mengunci pelaksanaan kepada seni bina pemproses platform tertentu . Tahap pemisahan tertinggi adalah antara penyusun statik dan dinamik. Dari sana, kami mempunyai pilihan bergantung pada lingkungan pelaksanaan yang kami sasarkan, hasil prestasi apa yang kami inginkan, dan sekatan sumber daya apa yang perlu kami penuhi. Saya secara ringkas membincangkan penyusun statik dan dinamik dalam Bahagian 1 siri ini. Dalam bahagian berikut saya akan menerangkan sedikit lagi.

Penyusunan statik vs dinamik

Contoh penyusun statik adalah yang disebut sebelumnya javac. Dengan penyusun statik, kod input ditafsirkan sekali dan output yang dapat dilaksanakan adalah dalam bentuk yang akan digunakan ketika program dijalankan. Kecuali anda membuat perubahan pada sumber asal anda dan menyusun semula kod (menggunakan penyusun), output akan selalu menghasilkan hasil yang sama; ini kerana input adalah input statik dan penyusunnya adalah penyusun statik.

Dalam penyusunan statik, kod Java berikut

static int add7( int x ) { return x+7; }

akan menghasilkan sesuatu yang serupa dengan kod bytec ini:

iload0 bipush 7 iadd ireturn

Penyusun dinamik diterjemahkan dari satu bahasa ke bahasa yang lain secara dinamik, yang bermaksud bahawa ia berlaku semasa kod tersebut dilaksanakan - semasa runtime! Penyusunan dan pengoptimuman dinamik memberi kelebihan pada masa berjalan kerana dapat menyesuaikan diri dengan perubahan dalam beban aplikasi. Pengompil dinamik sangat sesuai dengan waktu berjalan Java, yang biasanya dijalankan dalam lingkungan yang tidak dapat diramalkan dan selalu berubah. Sebilangan besar JVM menggunakan penyusun dinamik seperti penyusun Just-In-Time (JIT). Tangkapannya adalah bahawa penyusun dinamik dan pengoptimuman kod kadang-kadang memerlukan struktur data, utas, dan sumber daya CPU tambahan. Semakin maju pengoptimuman atau analisis konteks bytecode, semakin banyak sumber yang digunakan oleh penyusunan. Di kebanyakan persekitaran, overhead masih sangat kecil berbanding dengan peningkatan prestasi yang signifikan dari kod output.

Varieti JVM dan kebebasan platform Java

Semua implementasi JVM mempunyai satu kesamaan, iaitu usaha mereka agar bytecode aplikasi diterjemahkan ke dalam arahan mesin. Beberapa JVM menafsirkan kod aplikasi semasa memuat dan menggunakan pembilang prestasi untuk memfokuskan pada kod "panas". Beberapa JVM melangkau tafsiran dan hanya bergantung pada penyusunan. Keintensifan kompilasi sumber boleh menjadi hit yang lebih besar (terutama untuk aplikasi sisi pelanggan) tetapi ia juga membolehkan pengoptimuman yang lebih maju. Lihat Sumber untuk maklumat lebih lanjut.

Sekiranya anda seorang pemula ke Java, selok-belok JVM akan banyak membungkus kepala anda. Berita baiknya ialah anda tidak perlu! JVM menguruskan penyusunan dan pengoptimuman kod, jadi anda tidak perlu risau tentang arahan mesin dan cara penulisan kod aplikasi yang optimum untuk seni bina platform yang mendasari.

Dari Java bytecode hingga pelaksanaan

Setelah kod Java anda disusun menjadi bytecode, langkah seterusnya adalah menerjemahkan arahan bytecode ke kod mesin. Ini boleh dilakukan oleh jurubahasa atau penyusun.

Tafsiran

Bentuk penyusunan bytecode yang paling sederhana dipanggil interpretasi. An jurubahasa hanya memandang ke atas arahan perkakasan untuk setiap arahan bytecode dan menghantar ia di luar untuk dilaksanakan oleh CPU.

Anda boleh memikirkan penafsiran serupa dengan menggunakan kamus: untuk kata tertentu (arahan bytecode) ada terjemahan yang tepat (arahan kod mesin). Oleh kerana jurubahasa membaca dan segera melaksanakan satu arahan bytecode pada satu masa, tidak ada peluang untuk mengoptimumkan petunjuk yang ditetapkan. Jurubahasa juga harus melakukan tafsiran setiap kali bytecode dipanggil, yang menjadikannya agak lambat. Tafsiran adalah cara yang tepat untuk melaksanakan kod, tetapi set arahan output yang tidak dioptimumkan kemungkinan tidak akan menjadi urutan berprestasi tertinggi untuk pemproses platform sasaran.

Penyusunan

A pengkompil pada beban Sebaliknya keseluruhan kod untuk dilaksanakan ke dalam runtime. Oleh kerana ia menerjemahkan kod bytec, ia dapat melihat konteks keseluruhan atau sebahagian masa proses dan membuat keputusan mengenai bagaimana menterjemahkan kod tersebut. Keputusannya berdasarkan analisis grafik kod seperti cabang pelaksanaan arahan yang berbeza dan data konteks jangka masa.

Apabila urutan bytecode diterjemahkan ke dalam set arahan kod mesin dan pengoptimuman dapat dilakukan pada set instruksi ini, set instruksi pengganti (misalnya, urutan yang dioptimumkan) disimpan ke dalam struktur yang disebut cache kod . Pada masa berikutnya bahawa bytecode dijalankan, kod yang dioptimumkan sebelumnya dapat segera ditempatkan di cache kod dan digunakan untuk pelaksanaan. Dalam beberapa kes, pembilang prestasi mungkin masuk dan mengatasi pengoptimuman sebelumnya, dalam hal ini penyusun akan menjalankan urutan pengoptimuman baru. Kelebihan cache kod adalah set arahan yang dihasilkan dapat dilaksanakan sekaligus - tidak perlu pencarian interpretasi atau kompilasi! Ini mempercepat masa pelaksanaan, terutama untuk aplikasi Java di mana kaedah yang sama disebut berkali-kali.

Pengoptimuman

Bersama dengan penyusunan dinamik terdapat peluang untuk memasukkan pembilang prestasi. Penyusun mungkin, misalnya, memasukkan pembilang prestasiuntuk mengira setiap kali blok bytecode (misalnya, sesuai dengan kaedah tertentu) dipanggil. Penyusun menggunakan data tentang seberapa "panas" bytecode yang ditentukan untuk menentukan di mana pengoptimuman kod akan memberi kesan terbaik pada aplikasi yang sedang berjalan. Data profil waktu operasi membolehkan penyusun membuat banyak keputusan pengoptimuman kod dengan cepat, seterusnya meningkatkan prestasi pelaksanaan kod. Apabila data profil kod yang lebih halus tersedia, ia dapat digunakan untuk membuat keputusan pengoptimuman tambahan dan lebih baik, seperti: bagaimana cara urutan arahan yang lebih baik dalam bahasa yang dikompilasi ke, sama ada untuk mengganti sekumpulan arahan dengan set yang lebih cekap, atau bahkan sama ada untuk menghapuskan operasi berlebihan.

Contohnya

Pertimbangkan kod Java:

static int add7( int x ) { return x+7; }

Ini dapat disusun secara statik oleh javackod bytec:

iload0 bipush 7 iadd ireturn

Apabila kaedah dipanggil blok bytecode akan disusun secara dinamik kepada arahan mesin. Apabila penghitung prestasi (jika ada untuk blok kod) mencapai ambang, ia mungkin juga akan dioptimumkan. Hasil akhirnya kelihatan seperti instruksi mesin berikut untuk platform pelaksanaan tertentu:

lea rax,[rdx+7] ret

Penyusun yang berbeza untuk aplikasi yang berbeza

Aplikasi Java yang berbeza mempunyai keperluan yang berbeza. Aplikasi sisi pelayan perusahaan yang berjalan lama dapat memungkinkan pengoptimuman yang lebih banyak, sementara aplikasi sisi klien yang lebih kecil mungkin memerlukan pelaksanaan yang cepat dengan penggunaan sumber daya minimum. Mari kita pertimbangkan tiga tetapan penyusun yang berbeza dan kelebihan dan kekurangan masing-masing.

Penyusun sisi pelanggan

Penyusun pengoptimuman yang terkenal ialah C1, penyusun yang diaktifkan melalui -clientpilihan permulaan JVM. Seperti yang ditunjukkan oleh nama permulaannya, C1 adalah penyusun sisi pelanggan. Ia direka untuk aplikasi sisi pelanggan yang mempunyai sumber yang lebih sedikit dan, dalam banyak kes, sensitif terhadap masa permulaan aplikasi. C1 menggunakan pembilang prestasi untuk profil kod untuk membolehkan pengoptimuman yang sederhana dan tidak mengganggu.

Penyusun sisi pelayan

Untuk aplikasi yang sudah lama berjalan seperti aplikasi Java perusahaan sisi pelayan, penyusun sisi pelanggan mungkin tidak mencukupi. Penyusun sisi pelayan seperti C2 boleh digunakan sebagai gantinya. C2 biasanya diaktifkan dengan menambahkan pilihan permulaan JVM -serverke baris perintah permulaan anda. Oleh kerana kebanyakan program sisi pelayan dijangka berjalan untuk waktu yang lama, mengaktifkan C2 bermaksud anda akan dapat mengumpulkan lebih banyak data profil daripada yang anda lakukan dengan aplikasi klien ringan jangka pendek. Oleh itu, anda akan dapat menggunakan teknik dan algoritma pengoptimuman yang lebih maju.

Petua: Panaskan penyusun sisi pelayan anda

Untuk penyebaran sisi pelayan mungkin memerlukan sedikit masa sebelum penyusun mengoptimumkan bahagian awal "panas" kod, jadi penyebaran sisi pelayan sering memerlukan fasa "pemanasan". Sebelum melakukan apa-apa pengukuran prestasi pada penyebaran di sisi pelayan, pastikan aplikasi anda telah mencapai keadaan stabil! Membiarkan penyusun masa yang cukup untuk menyusun dengan betul akan bermanfaat untuk anda! (Lihat artikel JavaWorld "Perhatikan penyusun HotSpot Anda" untuk lebih banyak lagi mengenai pemanasan penyusun anda dan mekanisme pembuatan profil.)

Penyusun pelayan mengambil kira data profil lebih banyak daripada yang dilakukan oleh penyusun sisi pelanggan, dan memungkinkan analisis cabang yang lebih kompleks, yang bermaksud bahawa ia akan mempertimbangkan jalan pengoptimuman mana yang akan lebih bermanfaat. Mempunyai lebih banyak data profil yang ada menghasilkan hasil aplikasi yang lebih baik. Sudah tentu, membuat profil dan analisis yang lebih meluas memerlukan pengeluaran lebih banyak sumber pada penyusun. JVM dengan C2 yang diaktifkan akan menggunakan lebih banyak utas dan lebih banyak kitaran CPU, memerlukan cache kod yang lebih besar, dan sebagainya.

Penyusunan berjenjang

Penyusunan berjenjangmenggabungkan kompilasi sisi pelanggan dan pelayan. Azul pertama kali menyediakan kompilasi berjenjang dalam Zing JVMnya. Baru-baru ini (seperti Java SE 7) ia telah diadopsi oleh Oracle Java Hotspot JVM. Penyusunan bertingkat memanfaatkan kelebihan penyusun pelanggan dan pelayan dalam JVM anda. Penyusun pelanggan paling aktif semasa permulaan aplikasi dan menangani pengoptimuman yang dicetuskan oleh ambang prestasi-kaunter yang lebih rendah. Penyusun sisi pelanggan juga memasukkan pembilang prestasi dan menyediakan set arahan untuk pengoptimuman yang lebih maju, yang akan ditangani pada tahap kemudian oleh penyusun sisi pelayan. Penyusunan berjenjang adalah cara pembuatan profil yang sangat cekap sumber kerana penyusun dapat mengumpulkan data semasa aktiviti penyusun berimpak rendah, yang kemudiannya dapat digunakan untuk pengoptimuman yang lebih maju.Pendekatan ini juga menghasilkan lebih banyak maklumat daripada yang anda dapat dengan menggunakan kaunter profil kod yang ditafsirkan sahaja.

Skema carta dalam Gambar 1 menggambarkan perbezaan prestasi antara tafsiran murni, sisi pelanggan, sisi pelayan, dan kompilasi berjenjang. Paksi-X menunjukkan masa pelaksanaan (unit masa) dan prestasi paksi-Y (unit ops / masa).

Rajah 1. Perbezaan prestasi antara penyusun (klik untuk membesarkan)

Berbanding dengan kod yang ditafsirkan secara murni, menggunakan penyusun sisi pelanggan membawa kepada prestasi pelaksanaan sekitar 5 hingga 10 kali lebih baik (dalam operasi), sehingga meningkatkan prestasi aplikasi. Variasi keuntungan tentu saja bergantung pada seberapa efisien penyusunnya, pengoptimuman apa yang diaktifkan atau dilaksanakan, dan (pada tahap yang lebih rendah) seberapa baik aplikasi yang dirancang dengan baik berkenaan dengan platform pelaksanaan yang disasarkan. Yang terakhir sebenarnya adalah sesuatu yang tidak perlu dibimbangkan oleh pemaju Java.

Berbanding dengan penyusun sisi pelanggan, penyusun sisi pelayan biasanya meningkatkan prestasi kod sebanyak 30 peratus hingga 50 peratus yang dapat diukur. Dalam kebanyakan kes, peningkatan prestasi akan mengimbangkan kos sumber tambahan.