La domanda è (ora) sulla memorizzazione di molti dati, che possono essere rappresentati usando tipi primitivi come int
, in una Mappa. Alcune delle risposte qui sono molto fuorvianti secondo me. Vediamo perché.
Ho modificato il benchmark da trove a misurare sia il tempo di esecuzione che il consumo di memoria. Ho anche aggiunto PCJ a questo benchmark, che è un'altra libreria di raccolte per tipi primitivi (la uso ampiamente). Il benchmark "ufficiale" non confronta IntIntMaps con Java Collection Map<Integer, Integer>
, probabilmente l'archiviazione Integers
e l'archiviazione ints
non sono le stesse dal punto di vista tecnico. Ma a un utente potrebbe non interessare questo dettaglio tecnico, desidera archiviare i dati rappresentabili in modo ints
efficiente.
Innanzitutto la parte pertinente del codice:
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();
}
Presumo che i dati siano primitivi ints
, il che sembra sano. Ma ciò implica una penalità di runtime per java util, a causa del boxing automatico, che non è necessario per i framework di raccolte primitive.
I risultati di runtime (senza gc()
chiamate, ovviamente) su WinXP, jdk1.6.0_10:
100000 operazioni put 100000 contiene operazioni
raccolte java 1938 ms 203 ms
trove 234 ms 125 ms
pcj 516 ms 94 ms
Sebbene ciò possa già sembrare drastico, non è questo il motivo per utilizzare un tale framework.
Il motivo è la prestazione di memoria. I risultati per una mappa contenente 100000 int
voci:
Le raccolte java oscilla tra 6644536 e 7168840 byte
trove 1853296 byte
pcj 1866112 byte
Le raccolte Java richiedono una memoria tre volte superiore rispetto ai framework di raccolta primitivi. Vale a dire che è possibile conservare tre volte più dati in memoria, senza ricorrere all'IO del disco che riduce le prestazioni di runtime per grandezza. E questo conta. Leggi l'alta scalabilità per scoprire perché.
Nella mia esperienza, l'elevato consumo di memoria è il più grande problema di prestazioni con Java, che ovviamente comporta anche peggiori prestazioni di runtime. I framework di raccolta primitivi possono davvero aiutare qui.
Quindi: No, java.util non è la risposta. E "aggiungere funzionalità" alle raccolte Java non è il punto quando si chiede efficienza. Anche le moderne collezioni JDK non "superano nemmeno le collezioni specializzate Trove".
Disclaimer: il benchmark qui è tutt'altro che completo, né è perfetto. È pensato per portare a casa il punto, che ho sperimentato in molti progetti. Le raccolte primitive sono abbastanza utili per tollerare l'API fishy - se lavori con molti dati.