Bagaimana Anda menulis (dan menjalankan) benchmark mikro di Jawa?
Saya mencari beberapa contoh kode dan komentar yang menggambarkan berbagai hal untuk dipikirkan.
Contoh: Haruskah tolok ukur mengukur waktu / iterasi atau iterasi / waktu, dan mengapa?
java
jvm
benchmarking
jvm-hotspot
microbenchmark
John Nilsson
sumber
sumber
Jawaban:
Kiat tentang menulis tolok ukur mikro dari pencipta Java HotSpot :
Aturan 0: Baca makalah yang memiliki reputasi baik tentang JVM dan pembandingan-mikro. Yang bagus adalah Brian Goetz, 2005 . Jangan berharap terlalu banyak dari tolok ukur mikro; mereka hanya mengukur kisaran karakteristik kinerja JVM yang terbatas.
Aturan 1: Selalu sertakan fase pemanasan yang menjalankan kernel pengujian Anda sepenuhnya, cukup untuk memicu semua inisialisasi dan kompilasi sebelum fase waktu. (Lebih sedikit iterasi OK pada fase pemanasan. Aturan praktis adalah beberapa puluh ribu iterasi loop dalam.)
Aturan 2: Selalu jalankan dengan
-XX:+PrintCompilation
,,-verbose:gc
dll., Sehingga Anda dapat memverifikasi bahwa kompiler dan bagian lain dari JVM tidak melakukan pekerjaan yang tidak terduga selama fase pengaturan waktu Anda.Aturan 2.1: Mencetak pesan di fase awal dan akhir waktu dan pemanasan, sehingga Anda dapat memverifikasi bahwa tidak ada output dari Aturan 2 selama fase pengaturan waktu.
Aturan 3: Waspadai perbedaan antara
-client
dan-server
, dan OSR dan kompilasi reguler. The-XX:+PrintCompilation
bendera melaporkan kompilasi OSR dengan di-tanda untuk menunjukkan titik masuk non-awal, misalnya:Trouble$1::run @ 2 (41 bytes)
. Lebih suka server ke klien, dan reguler ke OSR, jika Anda mencari kinerja terbaik.Aturan 4: Waspadai efek inisialisasi. Jangan mencetak untuk pertama kalinya selama fase pengaturan waktu Anda, karena mencetak banyak dan menginisialisasi kelas. Jangan memuat kelas baru di luar fase pemanasan (atau fase pelaporan akhir), kecuali jika Anda menguji pemuatan kelas secara khusus (dan dalam hal ini memuat hanya kelas pengujian). Aturan 2 adalah garis pertahanan pertama Anda terhadap efek seperti itu.
Aturan 5: Waspadai efek deoptimisasi dan kompilasi. Jangan mengambil jalur kode apa pun untuk pertama kalinya dalam fase pengaturan waktu, karena kompilator dapat membuang dan mengkompilasi ulang kode tersebut, berdasarkan pada asumsi optimis sebelumnya bahwa jalur tersebut tidak akan digunakan sama sekali. Aturan 2 adalah garis pertahanan pertama Anda terhadap efek seperti itu.
Aturan 6: Gunakan alat yang tepat untuk membaca pikiran kompiler, dan berharap akan terkejut dengan kode yang dihasilkannya. Periksa kode Anda sendiri sebelum membentuk teori tentang apa yang membuat sesuatu lebih cepat atau lebih lambat.
Aturan 7: Kurangi kebisingan dalam pengukuran Anda. Jalankan benchmark Anda pada mesin yang tenang, dan jalankan beberapa kali, buang outlier. Gunakan
-Xbatch
untuk membuat serial kompiler dengan aplikasi, dan mempertimbangkan pengaturan-XX:CICompilerCount=1
untuk mencegah kompiler berjalan paralel dengan dirinya sendiri. Cobalah yang terbaik untuk mengurangi overhead GC, tetapkanXmx
(cukup besar) samaXms
dan gunakanUseEpsilonGC
jika tersedia.Aturan 8: Gunakan perpustakaan untuk patokan Anda karena mungkin lebih efisien dan sudah didebug untuk tujuan tunggal ini. Seperti JMH , Caliper atau Bill dan Tolok Ukur UCSD Paulus yang Sangat Baik untuk Java .
sumber
System.nanoTime()
tidak dijamin lebih akurat daripadaSystem.currentTimeMillis()
. Itu hanya dijamin setidaknya seakurat. Namun, biasanya ini jauh lebih akurat.System.nanoTime()
bukannyaSystem.currentTimeMillis()
bahwa yang pertama dijamin akan meningkat secara monoton. Mengurangkan nilai-nilai yang dikembalikan duacurrentTimeMillis
doa sebenarnya dapat memberikan hasil negatif, mungkin karena waktu sistem disesuaikan oleh beberapa daemon NTP.Saya tahu pertanyaan ini telah ditandai sebagai dijawab tetapi saya ingin menyebutkan dua perpustakaan yang membantu kami menulis tolok ukur mikro
Caliper dari Google
Tutorial memulai
JMH dari OpenJDK
Tutorial memulai
sumber
Hal-hal penting untuk tolok ukur Java adalah:
System.gc()
antara iterasi, ada baiknya menjalankannya di antara pengujian, sehingga setiap pengujian mudah-mudahan mendapatkan ruang memori "bersih" untuk digunakan. (Ya,gc()
lebih merupakan petunjuk daripada jaminan, tapi sangat mungkin itu benar-benar mengumpulkan sampah dalam pengalaman saya.)Saya hanya dalam proses blogging tentang desain kerangka kerja pembandingan di .NET. Aku punya beberapa dari posting sebelumnya yang mungkin dapat memberi Anda beberapa ide - tidak semuanya akan sesuai, tentu saja, tetapi beberapa mungkin.
sumber
gc
selalu membebaskan memori yang tidak terpakai.System.gc()
, bagaimana Anda mengusulkan untuk meminimalkan pengumpulan sampah dalam satu tes karena benda yang dibuat dalam tes sebelumnya? Saya pragmatis, bukan dogmatis.jmh adalah tambahan terbaru untuk OpenJDK dan telah ditulis oleh beberapa insinyur kinerja dari Oracle. Pasti layak untuk dilihat.
Potongan informasi yang sangat menarik terkubur dalam komentar tes sampel .
Lihat juga:
sumber
Itu tergantung pada apa yang Anda coba uji.
Jika Anda tertarik pada latensi , gunakan waktu / iterasi dan jika Anda tertarik pada throughput , gunakan iterasi / waktu.
sumber
Jika Anda mencoba membandingkan dua algoritma, lakukan setidaknya dua tolok ukur untuk masing-masing, secara bergantian urutan. yaitu:
Saya telah menemukan beberapa perbedaan yang nyata (kadang-kadang 5-10%) pada runtime dari algoritma yang sama pada lintasan yang berbeda ..
Juga, pastikan bahwa n sangat besar, sehingga runtime dari setiap loop setidaknya 10 detik. Semakin banyak iterasi, semakin banyak angka signifikan dalam waktu benchmark Anda dan semakin dapat diandalkan data itu.
sumber
Pastikan Anda entah bagaimana menggunakan hasil yang dihitung dalam kode benchmark. Kalau tidak, kode Anda dapat dioptimalkan.
sumber
Ada banyak kemungkinan jebakan untuk menulis tolok ukur mikro di Jawa.
Pertama: Anda harus menghitung dengan segala macam peristiwa yang membutuhkan waktu kurang lebih acak: Pengumpulan sampah, efek caching (OS untuk file dan CPU untuk memori), IO dll.
Kedua: Anda tidak dapat mempercayai keakuratan waktu yang diukur untuk interval yang sangat singkat.
Ketiga: JVM mengoptimalkan kode Anda saat menjalankan. Jadi menjalankan yang berbeda dalam instance JVM yang sama akan menjadi lebih cepat dan lebih cepat.
Rekomendasi saya: Jadikan benchmark Anda berjalan beberapa detik, yang lebih andal daripada runtime selama milidetik. Lakukan pemanasan JVM (artinya menjalankan patokan setidaknya satu kali tanpa pengukuran, agar JVM dapat menjalankan optimisasi). Dan jalankan benchmark Anda beberapa kali (mungkin 5 kali) dan ambil nilai median. Jalankan setiap micro-benchmark dalam JVM-instance baru (panggilan untuk setiap benchmark baru Java) jika tidak, efek optimisasi JVM dapat memengaruhi tes yang dijalankan nanti. Jangan jalankan hal-hal, yang tidak dieksekusi dalam fase pemanasan (karena ini dapat memicu beban kelas dan kompilasi ulang).
sumber
Perlu juga dicatat bahwa mungkin juga penting untuk menganalisis hasil dari tolok ukur mikro ketika membandingkan berbagai implementasi. Oleh karena itu tes signifikansi harus dilakukan.
Ini karena implementasi
A
mungkin lebih cepat selama sebagian besar proses benchmark dibandingkan implementasiB
. TetapiA
mungkin juga memiliki spread yang lebih tinggi, sehingga manfaat kinerja yang diukurA
tidak akan ada artinya jika dibandingkan denganB
.Jadi, penting juga untuk menulis dan menjalankan patokan mikro dengan benar, tetapi juga untuk menganalisisnya dengan benar.
sumber
Untuk menambah saran luar biasa lainnya, saya juga harus memperhatikan hal-hal berikut:
Untuk beberapa CPU (mis. Intel Core i5 range dengan TurboBoost), suhu (dan jumlah core yang saat ini digunakan, serta persen pemanfaatannya) mempengaruhi kecepatan clock. Karena CPU memiliki clock dinamis, ini dapat memengaruhi hasil Anda. Misalnya, jika Anda memiliki aplikasi single-threaded, kecepatan clock maksimum (dengan TurboBoost) lebih tinggi daripada aplikasi yang menggunakan semua core. Karena itu hal ini dapat mengganggu perbandingan kinerja tunggal dan multi-utas pada beberapa sistem. Ingatlah bahwa suhu dan gejolak juga mempengaruhi berapa lama frekuensi Turbo dipertahankan.
Mungkin aspek yang secara fundamental lebih penting yang Anda miliki untuk mengendalikan langsung: pastikan Anda mengukur hal yang benar! Misalnya, jika Anda menggunakan
System.nanoTime()
untuk menandai sedikit kode tertentu, lakukan panggilan ke tugas di tempat yang masuk akal untuk menghindari mengukur hal-hal yang tidak Anda minati. Misalnya, jangan lakukan:Masalahnya adalah Anda tidak segera mendapatkan waktu akhir ketika kode selesai. Alih-alih, coba yang berikut ini:
sumber
println
, bukan baris tajuk yang terpisah atau sesuatu, danSystem.nanoTime()
harus dievaluasi sebagai langkah pertama dalam membangun argumen string untuk panggilan itu. Tidak ada yang dapat dilakukan oleh kompiler dengan yang pertama yang tidak dapat mereka lakukan dengan yang kedua, dan tidak ada yang bahkan mendorong mereka untuk melakukan pekerjaan ekstra sebelum merekam waktu berhenti.http://opt.sourceforge.net/ Java Micro Benchmark - tugas kontrol yang diperlukan untuk menentukan karakteristik kinerja komparatif dari sistem komputer pada platform yang berbeda. Dapat digunakan untuk memandu keputusan pengoptimalan dan membandingkan berbagai implementasi Java.
sumber