Saya baru mengenal Java 8. Saya masih belum tahu tentang API secara mendalam, tetapi saya telah membuat tolok ukur informal kecil untuk membandingkan kinerja API Streams baru dengan Koleksi lama yang bagus.
Tes terdiri dalam menyaring daftar Integer
, dan untuk setiap nomor bahkan, menghitung akar kuadrat dan menyimpannya dalam hasil List
dari Double
.
Ini kodenya:
public static void main(String[] args) {
//Calculating square root of even numbers from 1 to N
int min = 1;
int max = 1000000;
List<Integer> sourceList = new ArrayList<>();
for (int i = min; i < max; i++) {
sourceList.add(i);
}
List<Double> result = new LinkedList<>();
//Collections approach
long t0 = System.nanoTime();
long elapsed = 0;
for (Integer i : sourceList) {
if(i % 2 == 0){
result.add(Math.sqrt(i));
}
}
elapsed = System.nanoTime() - t0;
System.out.printf("Collections: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Stream approach
Stream<Integer> stream = sourceList.stream();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Streams: Elapsed time:\t\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
//Parallel stream approach
stream = sourceList.stream().parallel();
t0 = System.nanoTime();
result = stream.filter(i -> i%2 == 0).map(i -> Math.sqrt(i)).collect(Collectors.toList());
elapsed = System.nanoTime() - t0;
System.out.printf("Parallel streams: Elapsed time:\t %d ns \t(%f seconds)%n", elapsed, elapsed / Math.pow(10, 9));
}.
Dan berikut adalah hasil untuk mesin dual core:
Collections: Elapsed time: 94338247 ns (0,094338 seconds)
Streams: Elapsed time: 201112924 ns (0,201113 seconds)
Parallel streams: Elapsed time: 357243629 ns (0,357244 seconds)
Untuk pengujian khusus ini, streaming sekitar dua kali lebih lambat dari koleksi, dan paralelisme tidak membantu (atau apakah saya menggunakannya dengan cara yang salah?).
Pertanyaan:
- Apakah tes ini adil? Apakah saya melakukan kesalahan?
- Apakah streaming lebih lambat dari koleksi? Adakah yang membuat tolok ukur formal yang bagus untuk ini?
- Pendekatan mana yang harus saya perjuangkan?
Hasil yang diperbarui.
Saya menjalankan tes 1k kali setelah pemanasan JVM (iterasi 1k) seperti yang disarankan oleh @pveentjer:
Collections: Average time: 206884437,000000 ns (0,206884 seconds)
Streams: Average time: 98366725,000000 ns (0,098367 seconds)
Parallel streams: Average time: 167703705,000000 ns (0,167704 seconds)
Dalam hal ini stream lebih performan. Saya ingin tahu apa yang akan diamati dalam aplikasi di mana fungsi penyaringan hanya dipanggil sekali atau dua kali selama runtime.
sumber
IntStream
bukan?toList
harus dijalankan secara paralel bahkan jika itu kumpulkan ke daftar non-thread-safe, karena utas yang berbeda akan mengumpulkan ke daftar perantara yang dibatasi utas sebelum digabungkan.Jawaban:
Hentikan penggunaan
LinkedList
untuk apa pun kecuali menghapus yang berat dari tengah daftar menggunakan iterator.Berhenti menulis kode benchmark dengan tangan, gunakan JMH .
Tolok ukur yang tepat:
Hasil:
Sama seperti yang saya harapkan implementasi aliran cukup lambat. JIT mampu meng-inline semua hal lambda tetapi tidak menghasilkan kode sesingkat versi vanilla.
Secara umum, aliran Java 8 tidak ajaib. Mereka tidak dapat mempercepat hal-hal yang sudah diimplementasikan dengan baik (dengan, mungkin, iterasi sederhana atau Java 5 untuk masing-masing pernyataan diganti
Iterable.forEach()
danCollection.removeIf()
panggilan). Streaming lebih banyak tentang pengkodean kenyamanan dan keamanan. Kenyamanan - tradeoff cepat bekerja di sini.sumber
@Benchmark
sebagai ganti@GenerateMicroBenchmark
1) Anda melihat waktu kurang dari 1 detik menggunakan tolok ukur Anda. Itu berarti ada pengaruh kuat efek samping pada hasil Anda. Jadi, saya meningkatkan tugas Anda 10 kali
dan jalankan patokan Anda. Hasil saya:
tanpa edit (
int max = 1_000_000
) hasilnya tadinyaIni seperti hasil Anda: streaming lebih lambat daripada koleksi. Kesimpulan: banyak waktu dihabiskan untuk inisialisasi aliran / transmisi nilai.
2) Setelah meningkatkan aliran tugas menjadi lebih cepat (tidak apa-apa), tetapi aliran paralel tetap terlalu lambat. Apa yang salah? Catatan: Anda ada
collect(Collectors.toList())
dalam perintah Anda. Mengumpulkan ke satu koleksi pada dasarnya memperkenalkan bottleneck dan overhead kinerja jika terjadi eksekusi bersamaan. Dimungkinkan untuk memperkirakan biaya relatif overhead dengan menggantiUntuk streaming, hal itu dapat dilakukan oleh
collect(Collectors.counting())
. Saya mendapat hasil:Itu untuk tugas besar! (
int max = 10000000
) Kesimpulan: mengumpulkan barang ke koleksi membutuhkan waktu yang lama. Bagian paling lambat ditambahkan ke daftar. BTW, sederhanaArrayList
digunakan untukCollectors.toList()
.sumber
collect(Collectors.toList())
dalam perintah Anda, yaitu mungkin ada situasi ketika Anda perlu mengatasi Koleksi tunggal dengan banyak utas. " Saya hampir yakin bahwatoList
mengumpulkan ke beberapa contoh daftar yang berbeda secara paralel. Hanya sebagai langkah terakhir dalam koleksi, elemen-elemen ditransfer ke satu daftar dan kemudian dikembalikan. Jadi seharusnya tidak ada sinkronisasi biaya overhead. Inilah sebabnya mengapa kolektor memiliki fungsi pemasok, akumulator, dan kombinator. (Bisa lambat karena alasan lain, tentu saja.)collect
implementasi di sini. Tetapi pada akhirnya beberapa daftar harus digabung menjadi satu, dan sepertinya penggabungan adalah operasi yang paling berat dalam contoh yang diberikan.Saya mengubah kode sedikit, berlari di mac book pro saya yang memiliki 8 core, saya mendapat hasil yang masuk akal:
Koleksi: Waktu yang berlalu: 1522036826 ns (1,522037 detik)
Streaming: Waktu yang berlalu: 4315833719 ns (4.315834 detik)
Aliran paralel: Waktu yang berlalu: 261152901 ns (0,261153 detik)
sumber
Untuk apa yang Anda coba lakukan, saya tidak akan menggunakan java api biasa. Ada satu ton tinju / unboxing yang sedang terjadi, jadi ada overhead kinerja yang sangat besar.
Secara pribadi saya pikir banyak API yang dirancang adalah omong kosong karena mereka membuat banyak objek sampah.
Coba gunakan array primitif double / int dan coba lakukan single threaded dan lihat kinerjanya.
PS: Anda mungkin ingin melihat JMH untuk melakukan benchmark. Ini menangani beberapa perangkap khas seperti pemanasan JVM.
sumber