Utilizzo della dimensione di raccolta per il confronto del ciclo

Esiste un ottimizzazione del compilatore per i metodi size () di Collections in Java?

Considera il seguente codice:

for(int i=0;i<list.size();i++) ...some operation..... 

C’è una chiamata ai metodi size () per ogni i. Non sarà meglio scoprire la dimensione e riutilizzarla? (Le chiamate al metodo hanno overheads).

 final int len = list.size() for(int i=0;i<len;i++) ...some operation..... 

Tuttavia, quando ho cronometrato entrambi questi pezzi di codice non c’era alcuna differenza di tempo significativa, anche per i 10000000. Mi manca qualcosa qui?

Update1: capisco che la dimensione non viene calcasting di nuovo a meno che la raccolta non cambi. Ma deve esserci un sovraccarico associato a una chiamata al metodo. È il caso che il compilatore li presenti sempre (vedi la risposta di Esko)?

Aggiornamento 2: la mia curiosità è stata alimentata ulteriormente. Dalle risposte fornite, vedo che i buoni compilatori JIT spesso inseriscono questa chiamata di funzione. Ma dovranno ancora determinare se la collezione è stata modificata o meno. Non accetto una risposta nella speranza che qualcuno mi fornisca indicazioni su come questo venga gestito dai compilatori.

Ok, ecco un estratto dai sorgenti JDK (src.zip nella cartella JDK):

 public int size() { return size; } 

Questo è da ArrayList, ma penso che altre collezioni abbiano implementazioni simili. Ora se immaginiamo che il compilatore indichi la chiamata a size (), che avrebbe perfettamente senso), il tuo ciclo si trasforma in questo:

 for(int i=0;i 

(Bene, dimentichiamo che la dimensione è privata.) Come controlla il compilatore se la collezione è stata modificata? La risposta è che non ha e non ha bisogno di farlo perché la dimensione è già disponibile nel campo, quindi tutto ciò che deve fare è accedere al campo dimensione su ogni iterazione, ma l'accesso a una variabile int è molto veloce operazione. Si noti che probabilmente calcola il suo indirizzo una volta, quindi non ha nemmeno l'elenco dei dereferenziamenti su ciascuna iterazione.

Cosa succede quando la collezione viene modificata, ad esempio, con il metodo add ()?

 public boolean add(E e) { ensureCapacity(size + 1); // Increments modCount!! elementData[size++] = e; return true; } 

Come puoi vedere, aumenta semplicemente il campo delle dimensioni. Quindi il compilatore in realtà non ha bisogno di fare nulla per assicurare che abbia accesso alle ultime dimensioni. L'unica eccezione è che se si modifica la raccolta da un altro thread è necessario sincronizzarsi, altrimenti il ​​thread del loop potrebbe vedere il suo valore di dimensione cache locale che può essere aggiornato o meno.

Il valore restituito dal metodo .size() della raccolta viene in genere memorizzato nella cache e ricalcolato solo quando la raccolta effettiva viene modificata ( vengono aggiunti nuovi elementi o rimossi quelli vecchi ).

Invece di confrontare for ambito del controllo del ciclo, provare a utilizzare for each ciclo poiché utilizza effettivamente Iterator che in alcune implementazioni di raccolta è molto più veloce dell’iterizzazione utilizzando l’indice.

Chiamare il metodo size () di una raccolta sta solo restituendo un valore intero di cui è già stata tenuta traccia. Non c’è molta differenza di tempo perché size () non sta effettivamente contando il numero di elementi ma invece il numero di elementi viene tenuto traccia di quando li aggiungi o rimuovi.

Le specifiche del linguaggio Java spiegano che l’espressione viene valutata in ogni fase di iterazione . Con l’esempio, list.size() viene chiamato 10.000.000 di volte.

Questo non ha importanza nel tuo caso, perché le implementazioni di elenchi (di solito) hanno un attributo privato che memorizza le dimensioni effettive dell’elenco. Ma può causare problemi, se la valutazione richiede davvero tempo. In questi casi è consigliabile memorizzare il risultato dell’espressione in una variabile locale.