Jadi awalnya, saya punya kode ini:
import java.util.*;
public class sandbox {
public static void main(String[] args) {
HashSet<Integer> hashSet = new HashSet<>();
for (int i = 0; i < 100_000; i++) {
hashSet.add(i);
}
long start = System.currentTimeMillis();
for (int i = 0; i < 100_000; i++) {
for (Integer val : hashSet) {
if (val != -1) break;
}
hashSet.remove(i);
}
System.out.println("time: " + (System.currentTimeMillis() - start));
}
}
Butuh sekitar 4s untuk menjalankan loop bersarang di komputer saya dan saya tidak mengerti mengapa butuh waktu lama. Loop luar berjalan 100.000 kali, bagian dalam untuk loop harus berjalan 1 kali (karena nilai hashSet tidak akan pernah -1) dan menghapus item dari HashSet adalah O (1), sehingga harus ada sekitar 200.000 operasi. Jika biasanya ada 100.000.000 operasi dalam satu detik, kenapa kode saya bisa berjalan 4s?
Selain itu, jika baris hashSet.remove(i);
dikomentari, kode hanya membutuhkan waktu 16 ms. Jika bagian dalam untuk loop dikomentari (tetapi tidak hashSet.remove(i);
), kode hanya membutuhkan 8 ms.
java
performance
for-loop
hashset
davidSC
sumber
sumber
for val
loop adalah hal yang menghabiskan waktu. Ituremove
masih sangat cepat. Beberapa jenis overhead mengatur iterator baru setelah set telah dimodifikasi ...?for val
loop lambat. Namun, perhatikan bahwa loop tidak diperlukan sama sekali. Jika Anda ingin memeriksa apakah ada nilai yang berbeda dari -1 di set, akan jauh lebih efisien untuk memeriksahashSet.size() > 1 || !hashSet.contains(-1)
.Jawaban:
Anda telah membuat kasus penggunaan marjinal
HashSet
, di mana algoritma menurun ke kompleksitas kuadratik.Inilah loop sederhana yang memakan waktu sangat lama:
async-profiler menunjukkan bahwa hampir semua waktu dihabiskan di dalam
java.util.HashMap$HashIterator()
konstruktor:Garis yang disorot adalah loop linier yang mencari ember non-kosong pertama di tabel hash.
Karena
Integer
memiliki trivialhashCode
(yaitu kode hash sama dengan angka itu sendiri), ternyata bilangan bulat berturut-turut sebagian besar menempati ember berurutan dalam tabel hash: angka 0 pergi ke ember pertama, angka 1 pergi ke ember pertama, angka 1 pergi ke ember kedua, dll.Sekarang Anda menghapus angka berurutan dari 0 hingga 99999. Dalam kasus paling sederhana (ketika ember berisi satu kunci) penghapusan kunci diimplementasikan sebagai meniadakan elemen yang sesuai dalam larik bucket. Perhatikan bahwa tabel tidak dipadatkan atau diulang setelah dilepas.
Jadi, semakin banyak kunci yang Anda hapus dari awal susunan bucket, semakin lama
HashIterator
kebutuhan untuk menemukan bucket non-kosong pertama.Cobalah untuk menghapus kunci dari ujung yang lain:
Algoritma akan menjadi lebih cepat secara dramatis!
sumber
if (i % 800 == 0) { hashSet = new HashSet<>(hashSet); }
.