Saya menjalankan kode Java berikut di laptop dengan 2,7 GHz Intel Core i7. Saya bermaksud untuk membiarkannya mengukur berapa lama waktu yang dibutuhkan untuk menyelesaikan satu loop dengan 2 ^ 32 iterasi, yang saya perkirakan kira-kira 1,48 detik (4 / 2,7 = 1,48).
Tapi sebenarnya hanya butuh 2 milidetik, bukan 1,48 s. Saya ingin tahu apakah ini adalah hasil dari pengoptimalan JVM di bawahnya?
public static void main(String[] args)
{
long start = System.nanoTime();
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++){
}
long finish = System.nanoTime();
long d = (finish - start) / 1000000;
System.out.println("Used " + d);
}
javap -v
untuk melihat.javac
melakukan pengoptimalan aktual yang sangat sedikit dan menyerahkan sebagian besar ke kompiler JIT.Jawaban:
Ada satu dari dua kemungkinan yang terjadi di sini:
Kompiler menyadari bahwa loop itu redundan dan tidak melakukan apa-apa sehingga dioptimalkan.
JIT (just-in-time compiler) menyadari bahwa loop itu mubazir dan tidak melakukan apa-apa, jadi itu dioptimalkan.
Penyusun modern sangat cerdas; mereka dapat melihat jika kode tidak berguna. Coba letakkan loop kosong ke GodBolt dan lihat outputnya, lalu aktifkan
-O2
pengoptimalan, Anda akan melihat bahwa outputnya adalah sesuatu di sepanjang barisSaya ingin menjelaskan sesuatu, di Jawa sebagian besar pengoptimalan dilakukan oleh JIT. Dalam beberapa bahasa lain (seperti C / C ++), sebagian besar pengoptimalan dilakukan oleh kompilator pertama.
sumber
Sepertinya itu dioptimalkan oleh kompiler JIT. Saat saya mematikannya (
-Djava.compiler=NONE
), kode berjalan lebih lambat:Saya memasukkan kode OP di dalam
class MyClass
.sumber
Saya hanya akan menyatakan yang sudah jelas - bahwa ini adalah pengoptimalan JVM yang terjadi, loop hanya akan dihapus sama sekali. Berikut adalah tes kecil yang menunjukkan perbedaan besar
JIT
ketika diaktifkan / diaktifkan hanya untukC1 Compiler
dan dinonaktifkan sama sekali.Penafian: jangan tulis pengujian seperti ini - ini hanya untuk membuktikan bahwa loop sebenarnya "penghapusan" terjadi di
C2 Compiler
:Hasilnya menunjukkan bahwa bergantung pada bagian mana dari yang
JIT
diaktifkan, metode menjadi lebih cepat (jauh lebih cepat sehingga terlihat seperti "tidak melakukan apa-apa" - penghapusan loop, yang tampaknya terjadi diC2 Compiler
- yang merupakan level maksimum):sumber
Seperti yang telah ditunjukkan, kompiler JIT (just-in-time) dapat mengoptimalkan loop kosong untuk menghapus iterasi yang tidak perlu. Tapi bagaimana caranya?
Sebenarnya, ada dua kompiler JIT: C1 & C2 . Pertama, kode dikompilasi dengan C1. C1 mengumpulkan statistik dan membantu JVM menemukan bahwa dalam 100% kasus loop kosong kami tidak mengubah apa pun dan tidak berguna. Dalam situasi ini C2 memasuki panggung. Ketika kode sangat sering dipanggil, itu dapat dioptimalkan dan dikompilasi dengan C2 menggunakan statistik yang dikumpulkan.
Sebagai contoh, saya akan menguji cuplikan kode berikutnya (JDK saya disetel ke slowdebug build 9-internal ):
Dengan opsi baris perintah berikut:
Dan ada versi berbeda dari metode run saya , dikompilasi dengan C1 dan C2 secara tepat. Bagi saya, varian terakhir (C2) terlihat seperti ini:
Ini sedikit berantakan, tetapi jika Anda melihat lebih dekat, Anda mungkin memperhatikan bahwa tidak ada putaran panjang di sini. Ada 3 blok: B1, B2 dan B3 dan langkah-langkah pelaksanaannya bisa
B1 -> B2 -> B3
atauB1 -> B3
. DimanaFreq: 1
- frekuensi perkiraan yang dinormalisasi dari eksekusi blok.sumber
Anda mengukur waktu yang dibutuhkan untuk mendeteksi loop tidak melakukan apa-apa, mengkompilasi kode di thread latar belakang dan menghilangkan kode.
Jika Anda menjalankan ini dengan
-XX:+PrintCompilation
Anda dapat melihat kode telah dikompilasi di latar belakang ke kompiler level 3 atau C1 dan setelah beberapa loop ke level 4 dari C4.Jika Anda mengubah loop untuk menggunakan
long
itu tidak bisa dioptimalkan.alih-alih Anda mendapatkan
sumber
long
penghitung mencegah pengoptimalan yang sama terjadi?int
note char dan short secara efektif sama pada level kode byte.Anda mempertimbangkan waktu mulai dan selesai dalam nanodetik dan Anda membaginya dengan 10 ^ 6 untuk menghitung latensi
seharusnya
10^9
karena1
detik =10^9
nanodetik.sumber