2015-07-23 33 views
5

Problem:Jak mogę ocenić implementację tablicy mieszającej? (Przy użyciu HashMap jako odniesienie)

  • muszę porównać 2 implementacje tabeli mieszania (dobrze zasadzie HashMap z innym) i dokonać rozsądnego wniosku.

  • Nie jestem zainteresowany w 100% dokładnością, ale po prostu we właściwym kierunku, w mojej ocenie.

  • Jestem zainteresowany tą różnicą, nie tylko za działania, ale przede wszystkim na hashtable jako „całość”.

  • nie mam ścisłe wymagania na szybkość więc jeżeli druga realizacja jest rozsądnie wolniej mogę to zaakceptować, ale zrobić oczekiwać/wymagać, aby być lepiej zużycie pamięci (ponieważ jeden z hashtables jest wspierane przez prymitywny stół).

co zrobiłem do tej pory:

Pierwotnie tworzę własne niestandardowe „benchmark” z pętli i wiele połączeń do zrozumienia dla GC, aby uzyskać poczucie różnicy ale czytam w internecie, że używanie standardowego narzędzia jest bardziej niezawodne/odpowiednie.
Przykład mojego podejścia (MapInterface tylko wrapper więc mogę przełączać się między implementacjami.):

int[] keys = new int[10000000]; 
String[] values = new String[10000000]; 
for(int i = 0; i < keys.length; ++i) { 
    keys[i] = i; 
    values[i] = "" + i; 
} 

if(operation.equals("put", keys, values)) { 
    runPutOperation(map); 
} 

public static long[] runOperation(MapInterface map, Integer[] keys, String[] values) { 
    long min = Long.MAX_VALUE; 
    long max = Long.MIN_VALUE; 
    long run = 0; 
    for(int i = 0; i < 10; ++i) { 
     long start = System.currentTimeMillis(); 
     for(int i = 0; i < keys.length; ++i) {   
      map.put(keys[i], values[i]); 
     } 
     long total = System.currentTimeMillis() - start; 
     System.out.println(total/1000d + " seconds");  
     if(total < min) { 
      min = time; 
     } 
     if(total > max) { 
      max = time; 
     } 
     run += time; 
     map = null; 
     map = createNewHashMap(); 
     hintsToGC();  
    } 
    return new long[] {min, max, run}; 
}  


public void hintsToGC() { 
    for(int i = 0; i < 20; ++i) { 
      System.out.print(". "); 
      System.gc();    
      try { 
       Thread.sleep(100); 
      } catch (InterruptedException e) {    
       e.printStackTrace(); 
      }   
     } 
} 


private HashMapInterface<String> createNewHashMap() { 
    if(jdk) { 
     return new JDKHashMapWrapper<String>(); 
    } 
    else { 
     return new AlternativeHashMapWrapper<String>(); 
    } 
} 



public class JDKHashMapWrapper implements HashMapInterface<String> { 
    HashMap<Integer, String> hashMap;   
    JDKHashMapWrapper() { 
     hashMap = new HashMap<Integer, String>(); 
    } 
    public String put(Integer key, String value) { 
     return hashMap.put(key, value); 
    } 
//etc 
} 

(Chcę przetestować put, get, contains oraz wykorzystanie pamięci)
Czy mogę mieć pewność, przez używając mojego podejścia, że ​​mogę uzyskać rozsądne pomiary?
Jeśli nie, jakie byłoby najbardziej odpowiednie narzędzie do użycia i jak?

Aktualizacja:
- Ja też przetestować liczb losowych z (także ~ 10M liczb losowych) z wykorzystaniem SecureRandom.
- Gdy tabeli mieszania zmienia rozmiar drukować logiczny rozmiar tabeli hash/wielkość rzeczywistej tabeli, aby uzyskać współczynnik obciążenia

Aktualizacja:
Na moim konkretnym przypadku, w którym jestem zainteresowany również w całkowitych jakie mogą być pułapki z moim podejściem?

UPDATE po @ dimo414 komentuje:

Dobrze przynajmniej hashtable jako "całość" nie ma sensu

Znaczy jak hashtable zachowuje się pod różnymi obciążeniami zarówno na środowisko uruchomieniowe i zużycie pamięci.

Każda struktura danych jest kompromis różnych metod

zgadzam.Moja kompromis jest do przyjęcia kary dostęp do poprawy pamięci

Należy określić, jakie funkcje jesteś zainteresowany weryfikacji

1) wprowadzenie (klucz, wartość);
2) get (klucz, wartość);
3) zawieraKey (klucz);
4) wszystkie powyższe, gdy wiele wpisów w tabeli mieszania

+0

Jedną z rzeczy, którą można zrobić, byłoby użycie System.nanoTime() zamiast System.currentTimeMillis(). Jest lepiej przystosowany do tego typu testów porównawczych. – bhspencer

+2

Ufam, że widziałeś http://stackoverflow.com/q/504103/113632? – dimo414

+0

@ dimo414: Mam. 1) Zaleca stosowanie dodatkowych opcji JVM, więc domyślam się, że moje podejście do opcji JVM można połączyć, aby uzyskać większą pewność. 2) Sprawdziłem frameworki w ostatniej regule. 'Bill i Paul's etc' ma prawie takie samo jak to, co robię. Caliper jest dla mnie, który jest pierwszym użytkownikiem i niezbyt doświadczonym w testowaniu czarnej skrzynki z niezbyt pomocną dokumentacją i daje najwyraźniej mikro-ławki na operację. Nie mam pojęcia, jak będzie testowany stół mieszający. JHM szczerze mówiąc Muszę przeczytać, czy może mi pomóc, czy nie – Cratylus

Odpowiedz

0

Właśnie robiłem coś podobnego do tego i skończyło się na użyciu wbudowanego profilera w Netbeans IDE. Możesz uzyskać naprawdę szczegółowe informacje na temat wykorzystania procesora i pamięci. Oryginalnie napisałem cały mój kod w Eclipse, ale Netbeans ma funkcję importu do wprowadzania projektów Eclipse i nie stanowi problemu, jeśli jest to prawdopodobnie Twoja sytuacja.

Jeśli chodzi o czas, możesz również zapoznać się z klasą StopWatch na Apache Commons. Jest to o wiele bardziej intuicyjny sposób śledzenie czasu na ukierunkowanych działań, np:

StopWatch myMapTimer = new StopWatch(); 
HashMap<Integer, Integer> hashMap = new HashMap<>(); 

myMapTimer.start(); 
for (int i = 0; i < numElements; i++) 
    hashMap.put(i, i); 
myMapTimer.stop(); 

System.out.println(myMapTimer.getTime()); // time will be in milliseconds 
+0

Czy są jakieś inne korzyści oprócz czystszego kodu za pomocą StopWatch? – Cratylus

+0

Nie jestem tego świadomy, ale ogólnie lubię używać ustalonego API, ograniczam głupie błędy. Są też inne klasy StopWatch w Guava i Spring Framework. – aconkey

1

Niektóre kluczową kwestią dla użyciu tabel mieszania jest wielkość „wiadra” przydziału, strategia rozdzielczości kolizji, a kształt twoich danych . Zasadniczo tabela mieszania przyjmuje klucz dostarczony przez aplikację, a następnie przypisuje ją do wartości mniejszej lub równej liczbie przydzielonych segmentów. Gdy dwie wartości kluczy są mieszane z tym samym zasobnikiem, implementacja musi rozwiązać kolizję i zwrócić właściwą wartość. Można na przykład posortować połączoną listę dla każdego wiadra i przeszukać tę listę.

Jeśli Twoje dane będą miały dużo kolizji, wydajność będzie spadać, ponieważ implementacja tabeli skrótu wyda zbyt dużo czasu na rozwiązanie kolizji. Z drugiej strony, jeśli masz bardzo dużo wiader, rozwiązujesz problem kolizji kosztem pamięci. Ponadto wbudowana implementacja HashMap Java będzie "ponownie uruchamiana", jeśli liczba wpisów będzie większa niż pewna ilość - wyobrażam sobie, że jest to droga operacja, której warto unikać.

Ponieważ twoje kluczowe dane to liczby całkowite dodatnie od 1 do 10 M, Twoje dane testowe wyglądają dobrze. Zapewniam również, że różne implementacje tabel mieszania zostały zainicjowane na tym samym rozmiarze wiadra dla danego testu, w przeciwnym razie nie jest to rzetelne porównanie. Na koniec zmieniam rozmiar wiadra na dość znaczny zakres i ponownie testuję, aby zobaczyć, jak implementacje zmieniły swoje zachowanie.

+0

Ważne punkty. Być może powinienem zaktualizować OP 1) Testuję również z liczbami losowymi (również ~ 10M liczb losowych) za pomocą SecureRandom. 2) Gdy tabela mieszania zostanie zmieniona, wydrukuję rozmiar logiczny tabeli mieszania/rozmiar rzeczywistej tabeli, aby uzyskać współczynnik obciążenia – Cratylus

+0

@ Catylus Czy aplikacja będzie używać liczb całkowitych jako klucza dla HashMap? – schtever

+0

Yest tylko liczby całkowite – Cratylus

1

Jak rozumiem, interesuje Cię zarówno czas wykonania operacji, jak i zużycie pamięci map w teście.

Zacznę od zużycia pamięci, ponieważ te połączenia nie będą w ogóle odbierane. Proponuję użyć małej biblioteki o nazwie Classmexer. Osobiście go używałem, gdy potrzebuję uzyskać 100% poprawne zużycie pamięci dowolnego obiektu. Ma podejście do agenta java (ponieważ używa API Instrumentation), co oznacza, że ​​trzeba dodać go jako parametr do JVM wykonywania swoich badań:

-javaagent: [PATH_TO]/classmexer.jar 

Korzystanie z Classmexer jest bardzo prosta. W dowolnym momencie można uzyskać zużycie pamięci w bajtach, wykonując:

MemoryUtil.deepMemoryUsageOf(mapIamInterestedIn, VisibilityFilter.ALL) 

Należy zauważyć, że z filtrem widoczności można określić, czy obliczanie pamięci powinno być zrobione dla obiektu (mapa) plus wszystkie inne osiągalnego obiektu poprzez referencje.Oto, do czego służy VisibilityFilter.ALL. Jednakże oznacza to, że otrzymany rozmiar obejmuje wszystkie obiekty używane do kluczy i wartości. Zatem jeśli masz 100 Integer/String wpisów, raportowany rozmiar również będzie obejmował te.

Dla aspektu czasowego proponuję narzędzie JMH, ponieważ to narzędzie jest przeznaczone do mikrodrutowania. Istnieje wiele przykładów online, na przykład this article ma przykłady testowania map, które mogą Cię całkiem nieźle poprowadzić.

Należy pamiętać, że powinienem zachować ostrożność, kiedy wywołasz ClassMexer's Memory Util, ponieważ zakłóci to czas, jeśli zadzwonisz podczas pomiaru czasu. Ponadto jestem pewien, że istnieje wiele innych narzędzi podobnych do Classmexer, ale lubię to, ponieważ są małe i proste.