Bagaimana cara saya menulis benchmark mikro di Jawa?

870

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?

Terkait: Apakah pembandingan stopwatch dapat diterima?

John Nilsson
sumber
Lihat [pertanyaan ini] [1] dari beberapa menit yang lalu untuk beberapa info terkait. sunting: maaf, ini tidak seharusnya menjadi jawaban. Saya seharusnya memposting sebagai komentar. [1]: stackoverflow.com/questions/503877/…
Tiago
Setelah merencanakan untuk merujuk poster pertanyaan itu ke pertanyaan seperti ini, saya mencatat bahwa pertanyaan ini tidak ada. Jadi ini dia, semoga akan mengumpulkan beberapa tips yang baik dari waktu ke waktu.
John Nilsson
5
Java 9 mungkin menyediakan beberapa fitur untuk pembandingan-mikro: openjdk.java.net/jeps/230
Raedwald
1
@Raedwald Saya berpikir bahwa JEP bertujuan untuk menambahkan beberapa tolok ukur mikro ke kode JDK, tapi saya tidak berpikir bahwa jmh akan dimasukkan dalam JDK ...
assylias
1
@Raedwald Halo dari masa depan. Itu tidak membuat luka .
Michael

Jawaban:

787

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:gcdll., 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 -clientdan -server, dan OSR dan kompilasi reguler. The -XX:+PrintCompilationbendera 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 -Xbatchuntuk membuat serial kompiler dengan aplikasi, dan mempertimbangkan pengaturan -XX:CICompilerCount=1untuk mencegah kompiler berjalan paralel dengan dirinya sendiri. Cobalah yang terbaik untuk mengurangi overhead GC, tetapkan Xmx(cukup besar) sama Xmsdan gunakan UseEpsilonGCjika 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 .

Eugene Kuleshov
sumber
5
Ini juga merupakan artikel yang menarik: ibm.com/developerworks/java/library/j-jtp12214
John Nilsson
143
Juga, jangan pernah gunakan System.currentTimeMillis () kecuali Anda OK dengan keakuratan + atau - 15 ms, yang tipikal pada sebagian besar kombinasi OS + JVM. Gunakan System.nanoTime () sebagai gantinya.
Scott Carey
5
Beberapa makalah dari javaOne: azulsystems.com/events/javaone_2009/session/…
bestsss
94
Perlu dicatat bahwa System.nanoTime()tidak dijamin lebih akurat daripada System.currentTimeMillis(). Itu hanya dijamin setidaknya seakurat. Namun, biasanya ini jauh lebih akurat.
Gravity
41
Alasan utama mengapa seseorang harus menggunakan System.nanoTime()bukannya System.currentTimeMillis()bahwa yang pertama dijamin akan meningkat secara monoton. Mengurangkan nilai-nilai yang dikembalikan dua currentTimeMillisdoa sebenarnya dapat memberikan hasil negatif, mungkin karena waktu sistem disesuaikan oleh beberapa daemon NTP.
Waldheinz
239

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

  1. http://codingjunkie.net/micro-benchmarking-with-caliper/
  2. http://vertexlabs.co.uk/blog/caliper

JMH dari OpenJDK

Tutorial memulai

  1. Menghindari Kesalahan Pembandingan pada JVM
  2. http://nitschinger.at/Using-JMH-for-Java-Microbenchmarking
  3. http://java-performance.info/jmh/
Aravind Yarram
sumber
37
Memberinya +1 dapat ditambahkan sebagai Aturan 8 dari jawaban yang diterima: Aturan 8: karena begitu banyak hal yang salah, Anda mungkin harus menggunakan perpustakaan yang ada daripada mencoba melakukannya sendiri!
assylias
8
@Pangea jmh mungkin lebih unggul daripada Caliper saat ini, Lihat juga: groups.google.com/forum/#!msg/mechanical-sympathy/m4opvy4xq3U/…
assylias
87

Hal-hal penting untuk tolok ukur Java adalah:

  • Lakukan pemanasan JIT terlebih dahulu dengan menjalankan kode beberapa kali sebelum penghitungan waktu itu
  • Pastikan Anda menjalankannya cukup lama untuk dapat mengukur hasil dalam hitungan detik atau (lebih baik) puluhan detik
  • Meskipun Anda tidak dapat melakukan panggilan di 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 suka menampilkan iterasi dan waktu, dan skor waktu / iterasi yang dapat diskalakan sedemikian rupa sehingga algoritma "terbaik" mendapat skor 1,0 dan yang lainnya diberi skor secara relatif. Ini berarti Anda dapat menjalankan semua algoritma untuk waktu yang lama, memvariasikan jumlah iterasi dan waktu, tetapi masih mendapatkan hasil yang sebanding.

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.

Jon Skeet
sumber
3
Minor nitpick: IMO "sehingga setiap tes mendapat" harus "sehingga setiap tes mungkin" karena yang pertama memberi kesan bahwa panggilan gc selalu membebaskan memori yang tidak terpakai.
Sanjay T. Sharma
@ SanjayT.Sharma: Ya, maksudnya adalah itu benar-benar terjadi. Meskipun tidak dijamin sepenuhnya, itu sebenarnya petunjuk yang cukup kuat. Akan diedit menjadi lebih jelas.
Jon Skeet
1
Saya tidak setuju dengan panggilan System.gc (). Itu adalah petunjuk, itu saja. Bahkan "itu diharapkan akan melakukan sesuatu". Anda seharusnya tidak pernah menyebutnya. Ini pemrograman, bukan seni.
gyorgyabraham
13
@gyabraham: Ya, itu petunjuk - tapi itu yang saya amati biasanya diambil. Jadi, jika Anda tidak suka menggunakan System.gc(), bagaimana Anda mengusulkan untuk meminimalkan pengumpulan sampah dalam satu tes karena benda yang dibuat dalam tes sebelumnya? Saya pragmatis, bukan dogmatis.
Jon Skeet
9
@gyabraham: Saya tidak tahu apa yang Anda maksud dengan "fallback besar". Bisakah Anda menguraikan, dan lagi - apakah Anda punya proposal untuk memberikan hasil yang lebih baik? Saya secara eksplisit mengatakan bahwa itu bukan jaminan ...
Jon Skeet
48

jmh adalah tambahan terbaru untuk OpenJDK dan telah ditulis oleh beberapa insinyur kinerja dari Oracle. Pasti layak untuk dilihat.

Jmh adalah harness Java untuk membangun, menjalankan, dan menganalisis tolok ukur nano / mikro / makro yang ditulis dalam Java dan bahasa lain yang menargetkan JVM.

Potongan informasi yang sangat menarik terkubur dalam komentar tes sampel .

Lihat juga:

assylias
sumber
1
Lihat juga posting blog ini: psy-lob-saw.blogspot.com/2013/04/... untuk detail tentang memulai dengan JMH.
Nitsan Wakart
FYI, JEP 230: Microbenchmark Suite adalah proposal OpenJDK berdasarkan proyek Java Microbenchmark Harness (JMH) ini. Tidak membuat potongan untuk Java 9 tetapi dapat ditambahkan nanti.
Basil Bourque
23

Haruskah tolok ukur mengukur waktu / iterasi atau iterasi / waktu, dan mengapa?

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.

Peter Lawrey
sumber
16

Jika Anda mencoba membandingkan dua algoritma, lakukan setidaknya dua tolok ukur untuk masing-masing, secara bergantian urutan. yaitu:

for(i=1..n)
  alg1();
for(i=1..n)
  alg2();
for(i=1..n)
  alg2();
for(i=1..n)
  alg1();

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.

Tidur
sumber
5
Mengubah urutan secara alami memengaruhi runtime. Optimalisasi dan caching efek JVM akan bekerja di sini. Lebih baik adalah 'memanaskan' optimasi JVM, membuat banyak proses dan mengukur setiap pengujian dalam JVM yang berbeda.
Mnementh
15

Pastikan Anda entah bagaimana menggunakan hasil yang dihitung dalam kode benchmark. Kalau tidak, kode Anda dapat dioptimalkan.

Peter Štibraný
sumber
13

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).

Mnementh
sumber
8

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 Amungkin lebih cepat selama sebagian besar proses benchmark dibandingkan implementasi B. Tetapi Amungkin juga memiliki spread yang lebih tinggi, sehingga manfaat kinerja yang diukur Atidak akan ada artinya jika dibandingkan dengan B.

Jadi, penting juga untuk menulis dan menjalankan patokan mikro dengan benar, tetapi juga untuk menganalisisnya dengan benar.

SpaceTrucker
sumber
8

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:

long startTime = System.nanoTime();
//code here...
System.out.println("Code took "+(System.nanoTime()-startTime)+"nano seconds");

Masalahnya adalah Anda tidak segera mendapatkan waktu akhir ketika kode selesai. Alih-alih, coba yang berikut ini:

final long endTime, startTime = System.nanoTime();
//code here...
endTime = System.nanoTime();
System.out.println("Code took "+(endTime-startTime)+"nano seconds");
Sina Madani
sumber
Ya, penting untuk tidak melakukan pekerjaan yang tidak terkait di dalam wilayah waktunya, tetapi contoh pertama Anda masih baik-baik saja. Hanya ada satu panggilan untuk println, bukan baris tajuk yang terpisah atau sesuatu, dan System.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.
Peter Cordes
7

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.

Yuriy
sumber
2
Tampaknya hanya benchmark perangkat keras JVM +, bukan sepotong kode Java sewenang-wenang.
Stefan L