2015-05-21 11 views
7

Ho riscontrato uno strano problema nella produzione di oggi. Anche se adoro Guava, mi sono imbattuto in un caso d'uso in cui lo Sets.intersection() di Guava si comportava male. Ho scritto un codice di esempio:Guava Sets.intersection prestazione errata

Set<Long> cache = new HashSet<>(); 
for (long i = 0; i < 1000000; i++) { 
    cache.add(i); 
} 
Set<Long> keys = new HashSet<>(); 
for (long i = 0; i < 100; i++) { 
    keys.add(i); 
} 
long start = System.currentTimeMillis(); 
Set<Long> foundKeys = new HashSet<>(); 
for (Long key : keys) { 
    if (cache.contains(key)) { 
     foundKeys.add(key); 
    } 
} 
System.out.println("Java search: " + (System.currentTimeMillis() - start)); 
start = System.currentTimeMillis(); 
SetView<Long> intersection = Sets.intersection(keys, cache); 
System.out.println("Guava search: " + (System.currentTimeMillis() - start)); 

Ho cercato di creare un simile scenario di produzione dove ho una cache chiavi, e sto cercando tutte le chiavi presenti nella cache. Stranamente, la ricerca di Guava richiede molto più tempo della ricerca Java. Dopo aver eseguito questo ho ottenuto:

Java search: 0 
Guava search: 36 

Chiunque può dire il motivo per cui questo non è adatto per il mio caso d'uso o c'è un bug nel Guava?

+1

si prega di dare un'occhiata a http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java –

+0

Sì, l'implementazione di Guava sembra essere asimmetrica: se il primo set è molto più grande del secondo, è molto più lento. Prova a cambiare i due set. – biziclop

+0

Sì, il mio primo set è già molto più piccolo. – Heisenberg

risposta

7

Si è verificato un problema con più chiamate a SetView.size(). Poiché SetView è una vista (in diretta) dell'intersezione dei due set, la dimensione dell'intersezione deve essere ricalcolata ogni volta.

public static <E> SetView<E> intersection(final Set<E> set1, final Set<?> set2) { 
//... 
    return new SetView<E>() { 
    @Override public Iterator<E> iterator() { 
     return Iterators.filter(set1.iterator(), inSet2); 
    } 
    @Override public int size() { 
     return Iterators.size(iterator()); 
    } 
    //... 
    }; 
} 

Come si può vedere qui, cosa ricalcolo significa in questo caso è l'iterazione attraverso l'intera vista, che può essere molto tempo.

Il modo per aggirare questo è quindi assicurarsi che size() venga chiamato una sola volta e il valore sia memorizzato (se si sa che i set sottostanti non cambieranno), o se ciò non è possibile, creare una copia dell'incrocio tramite ImmutableSet.copyOf() (ad esempio).

+0

Se il primo set che passiamo è più grande, solo noi abbiamo problemi con size(), altrimenti sembra che funzioni bene. – Heisenberg

+2

Si noti che 'SetView' stesso ha un metodo' immutableCopy() 'che restituisce un' ImmutableSet'. – ColinD

+0

@ColinD Hmm, non lo sapevo, sembra utile. Grazie per il consiglio. – biziclop