Leggibilità e prestazioni: è meglio consentire al garbage collector Java di cancellare una struttura dati?

2

Lavoro con HashMap e ArrayList di grandi dimensioni. Quando non necessitano più di essere in memoria, uso myArray.clear(); per liberare la memoria.

Quando il mio collega ha visto quella linea, l'ha cambiata in myArray = new ArrayList<>(); . Acconsentì quando gli chiesi se lo stava facendo per permettere al garbage collector di prendersene cura.

  1. Anche se sento che è bello, ho sentito che diminuisce la leggibilità. In qualche modo, clear() consente al maintainer di sapere che l'array viene cancellato. Una rapida occhiata a new ArrayList<>() potrebbe far pensare a una persona che un array sia stato inizializzato di nuovo lì.
  2. Il miglioramento delle prestazioni ne vale davvero la pena? Ho visto il codice sorgente di ArrayList, e il fatto che stiano iterando sulla lista di elementi da assegnare a loro null, mi ha fatto chiedere perché non potevano cancellare la memoria con una tecnica più veloce.

Implementazione di "clear":

 public void clear() {
     modCount++;
     // clear to let GC do its work
     for (int i = 0; i < size; i++) {elementData[i] = null;}
     size = 0;
 }

L'unico svantaggio che vedo usando new ArrayList<>() è che una nuova serie di posizioni contigue dovrebbe essere allocata in memoria. Forse questo rappresenterebbe un problema solo se non c'è abbastanza memoria residua, prima che il garbage collector possa chiarire il

    
posta Nav 21.04.2016 - 14:11
fonte

4 risposte

4

Sembra che tu abbia perso un'importante differenza tra myArray.clear() e myArray = new ArrayList<>() : il primo conserva la capacità dell'array, quindi non libera la memoria stessa dell'array. Verrà liberata solo la memoria degli oggetti a cui gli elementi del tuo array fanno riferimento (purché l'array abbia il solo riferimento a quegli oggetti).

Quindi se vuoi lasciare che il GC liberi l'intera memoria, usa meglio myArray = new ArrayList<>() . Ovviamente, la differenza sarà probabilmente trascurabile se si riempie immediatamente myArray con un numero simile di elementi rispetto a prima.

Is the performance improvement really worth it?

Bene, quale miglioramento delle prestazioni? Non è intrinsecamente chiaro quale dei due approcci sarà più veloce nel tuo caso d'uso. Il metodo clear può contenere un ciclo, ma se crei un nuovo arraylist che cresce nel tempo, non disponendo di una capacità preallocato, si verificherà una riallocazione che potrebbe determinare un impatto misurabile sulle prestazioni. Quindi, senza misurare, non si può dire in anticipo quale dei due approcci sarà più veloce. Per la maggior parte delle situazioni del mondo reale, mi aspetto che la differenza sia irrilevante, ma non conosciamo il tuo caso d'uso, e se le prestazioni sono importanti per il tuo caso, profilo dove è il collo di bottiglia, prova approcci diversi, misurali e confrontali. p>     

risposta data 21.04.2016 - 14:30
fonte
2

Una differenza importante tra entrambi gli approcci: se i riferimenti all'array sono memorizzati da qualche altra parte, quindi dopo myArray.clear tutti quelli che hanno un riferimento manterranno un riferimento a un array vuoto. Dopo aver assegnato un nuovo array, tutti conservano ancora un riferimento alla matrice originale.

    
risposta data 22.04.2016 - 17:44
fonte
1

Se la variabile myArray diventerà presto fuori portata, non preoccuparti di nessuno dei due metodi. Se hai effettivamente bisogno di cancellarlo, impostalo su null:

myArray = null

Se hai intenzione di riutilizzare la lista, la cancellazione ha vantaggi sulla creazione di una nuova lista, perché se la lista fosse lunga una volta potrebbe essere lunga una seconda volta, e la sua eliminazione eviterà un ridimensionamento interno della lista su usi futuri. Questo è un semplice compromesso tempo / spazio.

Avevo una struttura di dati basata su HashMap e ArrayList a memoria ridotta, ho scoperto che potevo risparmiare significativamente la memoria rappresentando liste (e mappe) di lunghezza 0, 1 e più grandi in modo diverso.

Per la lista a lunghezza zero, utilizzo semplicemente l'istanza Collections.emptyList (). Per un elenco di lunghezza, utilizzo Collections.singletonList (); Per due o più liste, utilizzo ArrayList.

Quindi, quando le cose vengono aggiunte o rimosse dagli elenchi, modifico il tipo di elenco di conseguenza. Lo stesso per le mappe. Ho trasformato questo comportamento in un'API, btw, che consente un utilizzo molto più limitato della memoria quando molte mappe o elenchi sono vuoti o contengono solo una singola istanza:

Documenti pertinenti:

link

link

JAR di origine (aocode-public): link

Ogni manipolazione di un elenco viene eseguita tramite un metodo statico in modo che possa modificare le istanze necessarie:

List<T> myList = Collections.emptyList();
// Add to the list
myList = MinimalList.add(myList, elem);

// All uses of the list can seem normal:
for(T elem : myList) { ... }

// Only changes to the list go through the static methods:
myList = MinimalList.remove(index);
    
risposta data 22.04.2016 - 07:36
fonte
1

Se gestisci elenchi potenzialmente grandi di dimensioni molto diverse che crescono e si riducono molto e sei preoccupato per l'allocazione della memoria, forse è un'opzione migliore per usare un LinkedList e cancellarlo alla fine.

Se le tue liste hanno principalmente le stesse dimensioni, cancella semplicemente ArrayList. Questo ha il leggero vantaggio in termini di prestazioni rispetto alla creazione di un nuovo elenco di non dover aumentare le dimensioni dell'array mentre lo si sta riempiendo, il che crea anche nuovi array da rimuovere dal GC. Inoltre, la cancellazione dell'elenco dà al GC un po 'meno da fare in quanto deve solo raccogliere gli elementi ma non la lista stessa.

    
risposta data 23.04.2016 - 18:58
fonte

Leggi altre domande sui tag