Pytanie dotyczy (teraz) przechowywania dużej ilości danych, które można przedstawić za pomocą typów prymitywnych, takich jak int
Map. Niektóre odpowiedzi są moim zdaniem bardzo mylące. Zobaczmy, dlaczego.
Zmodyfikowałem test porównawczy z trove, aby mierzyć zarówno czas wykonywania, jak i zużycie pamięci. Dodałem również PCJ do tego benchmarku, który jest kolejną biblioteką kolekcji dla typów prymitywnych (używam tego intensywnie). „Oficjalny” test porównawczy skarbów nie porównuje IntIntMaps z Java Collection Map<Integer, Integer>
, prawdopodobnie przechowywanie Integers
i przechowywanie ints
nie jest tym samym z technicznego punktu widzenia. Jednak użytkownik może nie przejmować się szczegółami technicznymi, za pomocą których chce przechowywać dane, które można przedstawićints
efektywnie .
Najpierw odpowiednia część kodu:
new Operation() {
private long usedMem() {
System.gc();
return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
}
// trove
public void ours() {
long mem = usedMem();
TIntIntHashMap ours = new TIntIntHashMap(SET_SIZE);
for ( int i = dataset.size(); i-- > 0; ) {
ours.put(i, i);
}
mem = usedMem() - mem;
System.err.println("trove " + mem + " bytes");
ours.clear();
}
public void pcj() {
long mem = usedMem();
IntKeyIntMap map = new IntKeyIntOpenHashMap(SET_SIZE);
for ( int i = dataset.size(); i-- > 0; ) {
map.put(i, i);
}
mem = usedMem() - mem;
System.err.println("pcj " + mem + " bytes");
map.clear();
}
// java collections
public void theirs() {
long mem = usedMem();
Map<Integer, Integer> map = new HashMap<Integer, Integer>(SET_SIZE);
for ( int i = dataset.size(); i-- > 0; ) {
map.put(i, i);
}
mem = usedMem() - mem;
System.err.println("java " + mem + " bytes");
map.clear();
}
Zakładam, że dane są prymitywne ints
, co wydaje się rozsądne. Ale to oznacza karę wykonawczą dla narzędzia java, ze względu na automatyczne boksowanie, które nie jest konieczne dla prymitywnych frameworków kolekcji.
Wyniki działania ( gc()
oczywiście bez wywołań) na WinXP, jdk1.6.0_10:
100000 operacji sprzedaży 100000 zawiera operacje
kolekcje java 1938 ms 203 ms
trove 234 ms 125 ms
PCJ 516 ms 94 ms
Chociaż może się to już wydawać drastyczne, nie jest to powód, aby korzystać z takiej struktury.
Powodem jest wydajność pamięci. Wyniki dla mapy zawierającej 100000 int
wpisów:
kolekcje java oscylują między 6644536 a 7168840 bajtów
skarb 1853296 bajtów
pcj 1866112 bajtów
Kolekcje Java wymagają ponad trzykrotnie większej ilości pamięci niż prymitywne struktury kolekcji. Oznacza to, że można przechowywać w pamięci trzy razy więcej danych, bez uciekania się do operacji we / wy dysku, co znacznie obniża wydajność środowiska wykonawczego. I to ma znaczenie. Przeczytaj artykuł o wysokiej skalowalności, aby dowiedzieć się, dlaczego.
Z mojego doświadczenia wynika, że duże zużycie pamięci jest największym problemem z wydajnością w Javie, co oczywiście skutkuje również gorszą wydajnością środowiska uruchomieniowego. Prymitywne struktury kolekcji mogą tu naprawdę pomóc.
A więc: nie, java.util nie jest odpowiedzią. A „dodawanie funkcjonalności” do kolekcji Java nie jest celem, gdy pytamy o wydajność. Również współczesne kolekcje JDK nie „wyprzedzają nawet wyspecjalizowanych kolekcji Trove”.
Zastrzeżenie: tutaj wzorzec jest daleki od ukończenia, ani też nie jest doskonały. Ma to na celu podkreślenie punktu, którego doświadczyłem w wielu projektach. Prymitywne kolekcje są wystarczająco przydatne, aby tolerować podejrzane API - jeśli pracujesz z dużą ilością danych.