2015-05-21 18 views
7

Napotkany dziś dziwny problem w produkcji. Chociaż kocham Guava, natknąłem się na przypadek użycia, w którym Guva Sets.intersection() radził sobie całkiem nieźle. Pisałem przykładowy kod:Guava SetsInsesection Bad Performance

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)); 

Starałem się stworzyć podobny scenariusz produkcyjną gdzie byłem cache klucze i szukam wszystkich przycisków znajdujących się w pamięci podręcznej. O dziwo, wyszukiwanie w Guava trwa znacznie dłużej niż wyszukiwanie w języku Java. Po uruchomieniu tego otrzymałem:

Java search: 0 
Guava search: 36 

Czy ktoś może powiedzieć, dlaczego nie jest to odpowiednie dla mojego przypadku użycia lub czy jest jakiś błąd w Guava?

+1

proszę spojrzeć na http://stackoverflow.com/questions/504103/how-do-i-write-a-correct-micro-benchmark-in-java –

+0

Tak, implementacja Guava jest asymetryczna: jeśli pierwszy zestaw jest dużo większy niż drugi, jest znacznie wolniejszy. Spróbuj przełączyć dwa zestawy. – biziclop

+0

Tak, mój pierwszy zestaw jest już znacznie mniejszy. – Heisenberg

Odpowiedz

7

Okazuje się, że problemem były wielokrotne połączenia z numerem SetView.size(). Ponieważ SetView jest widokiem (na żywo) przecięcia dwóch zestawów, rozmiar przecięcia musi zostać ponownie obliczony za każdym razem.

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()); 
    } 
    //... 
    }; 
} 

Jak widać tutaj, co znaczy przeliczanie w tym przypadku jest iteracja całej widzenia, który może być dość czasochłonne. Aby rozwiązać ten problem, należy się upewnić, że numer size() jest wywoływany tylko raz, a wartość jest przechowywana (jeśli znane zestawy nie ulegną zmianie) lub jeśli nie jest to możliwe, utwórz kopię. skrzyżowania przez: ImmutableSet.copyOf() (na przykład).

+0

Jeśli pierwszy zestaw, który przechodzimy, jest większy, to tylko my mamy problem z rozmiarem(), w przeciwnym razie wygląda na to, że działa dobrze. – Heisenberg

+2

Zauważ, że 'SetView' ma metodę' immutableCopy() ', która zwraca' ImmutableSet'. – ColinD

+0

@ColinD Hmm, tego nie wiedziałem, brzmi użytecznie. Dzięki za wskazówkę. – biziclop