W porządku, żeby to uspokoić, stworzyłem aplikację testową, aby uruchomić kilka scenariuszy i uzyskać wizualizacje wyników. Oto jak przeprowadzane są testy:
- Wypróbowano kilka różnych rozmiarów kolekcji: sto, tysiąc i sto tysięcy wpisów.
- Użyte klucze to instancje klasy, które są jednoznacznie identyfikowane przez identyfikator. Każdy test używa unikalnych kluczy, z rosnącymi liczbami całkowitymi jako identyfikatorami.
equals
Metoda wykorzystuje tylko identyfikator, więc żaden klawisz mapowanie zastępuje inną.
- Klucze otrzymują kod skrótu, który składa się z pozostałej części identyfikatora modułu w stosunku do pewnego wstępnie ustawionego numeru. Nazwiemy ten numer limitem skrótu . Pozwoliło mi to kontrolować liczbę oczekiwanych kolizji hashów. Na przykład, jeśli rozmiar naszej kolekcji to 100, będziemy mieć klucze z identyfikatorami w zakresie od 0 do 99. Jeśli limit skrótu wynosi 100, każdy klucz będzie miał unikalny kod skrótu. Jeśli limit skrótu wynosi 50, klucz 0 będzie miał ten sam kod skrótu co klucz 50, 1 będzie miał ten sam kod skrótu co 51 itd. Innymi słowy, oczekiwana liczba kolizji skrótu na klucz to rozmiar kolekcji podzielony przez skrót limit.
- Dla każdej kombinacji rozmiaru kolekcji i limitu mieszania uruchomiłem test przy użyciu map skrótów zainicjowanych z różnymi ustawieniami. Te ustawienia to współczynnik obciążenia i początkowa pojemność wyrażona jako współczynnik ustawienia zbierania. Na przykład test z rozmiarem kolekcji 100 i początkowym współczynnikiem pojemności 1,25 zainicjuje mapę skrótów o początkowej pojemności 125.
- Wartość każdego klucza jest po prostu nowa
Object
.
- Każdy wynik testu jest hermetyzowany w wystąpieniu klasy Result. Na końcu wszystkich testów wyniki są sortowane od najgorszych do najlepszych.
- Średni czas na puts i get jest obliczany na 10 puts / gets.
- Wszystkie kombinacje testowe są uruchamiane raz, aby wyeliminować wpływ kompilacji JIT. Następnie testy są uruchamiane w celu uzyskania rzeczywistych wyników.
Oto klasa:
package hashmaptest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
public class HashMapTest {
private static final List<Result> results = new ArrayList<Result>();
public static void main(String[] args) throws IOException {
final int[][] sampleSizesAndHashLimits = new int[][] {
{100, 50, 90, 100},
{1000, 500, 900, 990, 1000},
{100000, 10000, 90000, 99000, 100000}
};
final double[] initialCapacityFactors = new double[] {0.5, 0.75, 1.0, 1.25, 1.5, 2.0};
final float[] loadFactors = new float[] {0.5f, 0.75f, 1.0f, 1.25f};
for(int[] sizeAndLimits : sampleSizesAndHashLimits) {
int size = sizeAndLimits[0];
for(int i = 1; i < sizeAndLimits.length; ++i) {
int limit = sizeAndLimits[i];
for(double initCapacityFactor : initialCapacityFactors) {
for(float loadFactor : loadFactors) {
runTest(limit, size, initCapacityFactor, loadFactor);
}
}
}
}
results.clear();
for(int[] sizeAndLimits : sampleSizesAndHashLimits) {
int size = sizeAndLimits[0];
for(int i = 1; i < sizeAndLimits.length; ++i) {
int limit = sizeAndLimits[i];
for(double initCapacityFactor : initialCapacityFactors) {
for(float loadFactor : loadFactors) {
runTest(limit, size, initCapacityFactor, loadFactor);
}
}
}
}
Collections.sort(results);
for(final Result result : results) {
result.printSummary();
}
}
private static void runTest(final int hashLimit, final int sampleSize,
final double initCapacityFactor, final float loadFactor) {
final int initialCapacity = (int)(sampleSize * initCapacityFactor);
System.out.println("Running test for a sample collection of size " + sampleSize
+ ", an initial capacity of " + initialCapacity + ", a load factor of "
+ loadFactor + " and keys with a hash code limited to " + hashLimit);
System.out.println("====================");
double hashOverload = (((double)sampleSize/hashLimit) - 1.0) * 100.0;
System.out.println("Hash code overload: " + hashOverload + "%");
final List<Key> keys = generateSamples(hashLimit, sampleSize);
final List<Object> values = generateValues(sampleSize);
final HashMap<Key, Object> map = new HashMap<Key, Object>(initialCapacity, loadFactor);
final long startPut = System.nanoTime();
for(int i = 0; i < sampleSize; ++i) {
map.put(keys.get(i), values.get(i));
}
final long endPut = System.nanoTime();
final long putTime = endPut - startPut;
final long averagePutTime = putTime/(sampleSize/10);
System.out.println("Time to map all keys to their values: " + putTime + " ns");
System.out.println("Average put time per 10 entries: " + averagePutTime + " ns");
final long startGet = System.nanoTime();
for(int i = 0; i < sampleSize; ++i) {
map.get(keys.get(i));
}
final long endGet = System.nanoTime();
final long getTime = endGet - startGet;
final long averageGetTime = getTime/(sampleSize/10);
System.out.println("Time to get the value for every key: " + getTime + " ns");
System.out.println("Average get time per 10 entries: " + averageGetTime + " ns");
System.out.println("");
final Result result =
new Result(sampleSize, initialCapacity, loadFactor, hashOverload, averagePutTime, averageGetTime, hashLimit);
results.add(result);
System.gc();
try {
Thread.sleep(200);
} catch(final InterruptedException e) {}
}
private static List<Key> generateSamples(final int hashLimit, final int sampleSize) {
final ArrayList<Key> result = new ArrayList<Key>(sampleSize);
for(int i = 0; i < sampleSize; ++i) {
result.add(new Key(i, hashLimit));
}
return result;
}
private static List<Object> generateValues(final int sampleSize) {
final ArrayList<Object> result = new ArrayList<Object>(sampleSize);
for(int i = 0; i < sampleSize; ++i) {
result.add(new Object());
}
return result;
}
private static class Key {
private final int hashCode;
private final int id;
Key(final int id, final int hashLimit) {
this.id = id;
this.hashCode = id % hashLimit;
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(final Object o) {
return ((Key)o).id == this.id;
}
}
static class Result implements Comparable<Result> {
final int sampleSize;
final int initialCapacity;
final float loadFactor;
final double hashOverloadPercentage;
final long averagePutTime;
final long averageGetTime;
final int hashLimit;
Result(final int sampleSize, final int initialCapacity, final float loadFactor,
final double hashOverloadPercentage, final long averagePutTime,
final long averageGetTime, final int hashLimit) {
this.sampleSize = sampleSize;
this.initialCapacity = initialCapacity;
this.loadFactor = loadFactor;
this.hashOverloadPercentage = hashOverloadPercentage;
this.averagePutTime = averagePutTime;
this.averageGetTime = averageGetTime;
this.hashLimit = hashLimit;
}
@Override
public int compareTo(final Result o) {
final long putDiff = o.averagePutTime - this.averagePutTime;
final long getDiff = o.averageGetTime - this.averageGetTime;
return (int)(putDiff + getDiff);
}
void printSummary() {
System.out.println("" + averagePutTime + " ns per 10 puts, "
+ averageGetTime + " ns per 10 gets, for a load factor of "
+ loadFactor + ", initial capacity of " + initialCapacity
+ " for " + sampleSize + " mappings and " + hashOverloadPercentage
+ "% hash code overload.");
}
}
}
Uruchomienie tego może chwilę potrwać. Wyniki są drukowane standardowo. Możesz zauważyć, że zakomentowałem linię. Ta linia wywołuje wizualizator, który wyświetla wizualne reprezentacje wyników do plików png. Klasa do tego jest podana poniżej. Jeśli chcesz go uruchomić, odkomentuj odpowiednią linię w powyższym kodzie. Uwaga: klasa visualizer zakłada, że pracujesz w systemie Windows i utworzy foldery i pliki w C: \ temp. Podczas biegania na innej platformie dostosuj to.
package hashmaptest;
import hashmaptest.HashMapTest.Result;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.imageio.ImageIO;
public class ResultVisualizer {
private static final Map<Integer, Map<Integer, Set<Result>>> sampleSizeToHashLimit =
new HashMap<Integer, Map<Integer, Set<Result>>>();
private static final DecimalFormat df = new DecimalFormat("0.00");
static void visualizeResults(final List<Result> results) throws IOException {
final File tempFolder = new File("C:\\temp");
final File baseFolder = makeFolder(tempFolder, "hashmap_tests");
long bestPutTime = -1L;
long worstPutTime = 0L;
long bestGetTime = -1L;
long worstGetTime = 0L;
for(final Result result : results) {
final Integer sampleSize = result.sampleSize;
final Integer hashLimit = result.hashLimit;
final long putTime = result.averagePutTime;
final long getTime = result.averageGetTime;
if(bestPutTime == -1L || putTime < bestPutTime)
bestPutTime = putTime;
if(bestGetTime <= -1.0f || getTime < bestGetTime)
bestGetTime = getTime;
if(putTime > worstPutTime)
worstPutTime = putTime;
if(getTime > worstGetTime)
worstGetTime = getTime;
Map<Integer, Set<Result>> hashLimitToResults =
sampleSizeToHashLimit.get(sampleSize);
if(hashLimitToResults == null) {
hashLimitToResults = new HashMap<Integer, Set<Result>>();
sampleSizeToHashLimit.put(sampleSize, hashLimitToResults);
}
Set<Result> resultSet = hashLimitToResults.get(hashLimit);
if(resultSet == null) {
resultSet = new HashSet<Result>();
hashLimitToResults.put(hashLimit, resultSet);
}
resultSet.add(result);
}
System.out.println("Best average put time: " + bestPutTime + " ns");
System.out.println("Best average get time: " + bestGetTime + " ns");
System.out.println("Worst average put time: " + worstPutTime + " ns");
System.out.println("Worst average get time: " + worstGetTime + " ns");
for(final Integer sampleSize : sampleSizeToHashLimit.keySet()) {
final File sizeFolder = makeFolder(baseFolder, "sample_size_" + sampleSize);
final Map<Integer, Set<Result>> hashLimitToResults =
sampleSizeToHashLimit.get(sampleSize);
for(final Integer hashLimit : hashLimitToResults.keySet()) {
final File limitFolder = makeFolder(sizeFolder, "hash_limit_" + hashLimit);
final Set<Result> resultSet = hashLimitToResults.get(hashLimit);
final Set<Float> loadFactorSet = new HashSet<Float>();
final Set<Integer> initialCapacitySet = new HashSet<Integer>();
for(final Result result : resultSet) {
loadFactorSet.add(result.loadFactor);
initialCapacitySet.add(result.initialCapacity);
}
final List<Float> loadFactors = new ArrayList<Float>(loadFactorSet);
final List<Integer> initialCapacities = new ArrayList<Integer>(initialCapacitySet);
Collections.sort(loadFactors);
Collections.sort(initialCapacities);
final BufferedImage putImage =
renderMap(resultSet, loadFactors, initialCapacities, worstPutTime, bestPutTime, false);
final BufferedImage getImage =
renderMap(resultSet, loadFactors, initialCapacities, worstGetTime, bestGetTime, true);
final String putFileName = "size_" + sampleSize + "_hlimit_" + hashLimit + "_puts.png";
final String getFileName = "size_" + sampleSize + "_hlimit_" + hashLimit + "_gets.png";
writeImage(putImage, limitFolder, putFileName);
writeImage(getImage, limitFolder, getFileName);
}
}
}
private static File makeFolder(final File parent, final String folder) throws IOException {
final File child = new File(parent, folder);
if(!child.exists())
child.mkdir();
return child;
}
private static BufferedImage renderMap(final Set<Result> results, final List<Float> loadFactors,
final List<Integer> initialCapacities, final float worst, final float best,
final boolean get) {
final Color[][] map = new Color[initialCapacities.size()][loadFactors.size()];
for(final Result result : results) {
final int x = initialCapacities.indexOf(result.initialCapacity);
final int y = loadFactors.indexOf(result.loadFactor);
final float time = get ? result.averageGetTime : result.averagePutTime;
final float score = (time - best)/(worst - best);
final Color c = new Color(score, 1.0f - score, 0.0f);
map[x][y] = c;
}
final int imageWidth = initialCapacities.size() * 40 + 50;
final int imageHeight = loadFactors.size() * 40 + 50;
final BufferedImage image =
new BufferedImage(imageWidth, imageHeight, BufferedImage.TYPE_3BYTE_BGR);
final Graphics2D g = image.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, imageWidth, imageHeight);
for(int x = 0; x < map.length; ++x) {
for(int y = 0; y < map[x].length; ++y) {
g.setColor(map[x][y]);
g.fillRect(50 + x*40, imageHeight - 50 - (y+1)*40, 40, 40);
g.setColor(Color.BLACK);
g.drawLine(25, imageHeight - 50 - (y+1)*40, 50, imageHeight - 50 - (y+1)*40);
final Float loadFactor = loadFactors.get(y);
g.drawString(df.format(loadFactor), 10, imageHeight - 65 - (y)*40);
}
g.setColor(Color.BLACK);
g.drawLine(50 + (x+1)*40, imageHeight - 50, 50 + (x+1)*40, imageHeight - 15);
final int initialCapacity = initialCapacities.get(x);
g.drawString(((initialCapacity%1000 == 0) ? "" + (initialCapacity/1000) + "K" : "" + initialCapacity), 15 + (x+1)*40, imageHeight - 25);
}
g.drawLine(25, imageHeight - 50, imageWidth, imageHeight - 50);
g.drawLine(50, 0, 50, imageHeight - 25);
g.dispose();
return image;
}
private static void writeImage(final BufferedImage image, final File folder,
final String filename) throws IOException {
final File imageFile = new File(folder, filename);
ImageIO.write(image, "png", imageFile);
}
}
Wizualizowane dane wyjściowe są następujące:
- Testy są podzielone najpierw według rozmiaru kolekcji, a następnie według limitu skrótu.
- Dla każdego testu istnieje obraz wyjściowy przedstawiający średni czas wprowadzenia (na 10 wejść) i średni czas uzyskania (na 10 wejść). Obrazy są dwuwymiarowymi „mapami ciepła”, które pokazują kolor według kombinacji początkowej pojemności i współczynnika obciążenia.
- Kolory na obrazach są oparte na średnim czasie w znormalizowanej skali od najlepszego do najgorszego wyniku, od nasyconej zieleni do nasyconej czerwieni. Innymi słowy, najlepszy czas będzie w pełni zielony, a najgorszy - w pełni czerwony. Dwa różne pomiary czasu nigdy nie powinny mieć tego samego koloru.
- Mapy kolorów są obliczane osobno dla pozycji put i get, ale obejmują wszystkie testy dla odpowiednich kategorii.
- Wizualizacje pokazują początkową nośność na ich osi x, a współczynnik obciążenia na osi y.
Bez zbędnych ceregieli spójrzmy na wyniki. Zacznę od wyników dla puttów.
Umieść wyniki
Rozmiar kolekcji: 100. Limit skrótu: 50. Oznacza to, że każdy kod skrótu powinien wystąpić dwukrotnie, a każdy inny klucz koliduje na mapie skrótów.
Cóż, to nie zaczyna się zbyt dobrze. Widzimy, że istnieje duży hotspot dla początkowej pojemności 25% powyżej rozmiaru kolekcji, przy współczynniku obciążenia 1. Lewy dolny róg nie działa zbyt dobrze.
Rozmiar kolekcji: 100. Limit skrótu: 90. Jeden na dziesięć kluczy ma zduplikowany kod skrótu.
Jest to nieco bardziej realistyczny scenariusz, który nie ma idealnej funkcji skrótu, ale nadal jest przeciążony o 10%. Hotspot zniknął, ale połączenie małej pojemności początkowej z niskim współczynnikiem obciążenia oczywiście nie działa.
Wielkość kolekcji: 100. Limit mieszania: 100. Każdy klucz jako jego własny, unikalny kod skrótu. Nie oczekuje się kolizji, jeśli jest wystarczająca liczba łyżek.
Początkowa pojemność 100 przy współczynniku obciążenia 1 wydaje się w porządku. Zaskakujące jest, że wyższa pojemność początkowa przy niższym współczynniku obciążenia niekoniecznie jest dobra.
Wielkość kolekcji: 1000. Limit haszowania: 500. Tutaj robi się poważniej, z 1000 wpisów. Podobnie jak w pierwszym teście, występuje przeciążenie hashujące od 2 do 1.
Lewy dolny róg nadal nie radzi sobie dobrze. Wydaje się jednak, że istnieje symetria między kombinacją niższej liczby początkowej / wyższego współczynnika obciążenia i wyższego współczynnika początkowego / niskiego obciążenia.
Wielkość kolekcji: 1000. Limit mieszania: 900. Oznacza to, że jeden na dziesięć kodów skrótów wystąpi dwukrotnie. Rozsądny scenariusz dotyczący kolizji.
Jest coś bardzo zabawnego z nieprawdopodobną kombinacją początkowej pojemności, która jest zbyt niska przy współczynniku obciążenia powyżej 1, co jest raczej sprzeczne z intuicją. W przeciwnym razie nadal dość symetrycznie.
Wielkość kolekcji: 1000. Limit hash: 990. Trochę kolizji, ale tylko kilka. Całkiem realistyczne pod tym względem.
Mamy tutaj niezłą symetrię. Lewy dolny róg jest nadal nieoptymalny, ale kombinacje 1000 init pojemność / 1,0 współczynnik obciążenia w porównaniu z 1250 init pojemność / 0,75 współczynnik obciążenia są na tym samym poziomie.
Wielkość kolekcji: 1000. Limit mieszania: 1000. Brak zduplikowanych kodów skrótu, ale teraz z wielkością próbki 1000.
Niewiele do powiedzenia. Wydaje się, że połączenie wyższej nośności początkowej ze współczynnikiem obciążenia 0,75 nieznacznie przewyższa połączenie 1000 nośności początkowej ze współczynnikiem obciążenia 1.
Wielkość kolekcji: 100_000. Limit skrótu: 10_000. W porządku, teraz robi się poważnie, z próbką o wielkości stu tysięcy i 100 duplikatów kodu skrótu na klucz.
Yikes! Myślę, że znaleźliśmy nasze niższe spektrum. Zdolność inicjowania o wielkości kolekcji i współczynniku obciążenia równym 1 radzi sobie tutaj naprawdę dobrze, ale poza tym jest w całym sklepie.
Wielkość kolekcji: 100_000. Limit skrótu: 90_000. Trochę bardziej realistyczne niż w poprzednim teście, tutaj mamy 10% przeciążenie w kodach hashujących.
Lewy dolny róg jest nadal niepożądany. Wyższe początkowe pojemności działają najlepiej.
Wielkość kolekcji: 100_000. Limit skrótu: 99_000. To dobry scenariusz. Duża kolekcja z 1% przeciążeniem kodu skrótu.
Użycie dokładnego rozmiaru kolekcji jako pojemności początkowej przy współczynniku obciążenia 1 wygrywa tutaj! Nieco większe możliwości inicjalizacji działają jednak całkiem dobrze.
Wielkość kolekcji: 100_000. Limit skrótu: 100_000. Duży. Największa kolekcja z doskonałą funkcją skrótu.
Oto kilka zaskakujących rzeczy. Wydajność początkowa z 50% dodatkowym pomieszczeniem przy współczynniku obciążenia 1 wygrywa.
W porządku, to wszystko dla puttów. Teraz sprawdzimy, czy dostaje. Pamiętaj, że wszystkie poniższe mapy odnoszą się do najlepszych / najgorszych czasów zdobycia, czasy wstawiania nie są już brane pod uwagę.
Uzyskać rezultaty
Rozmiar kolekcji: 100. Limit skrótu: 50. Oznacza to, że każdy kod skrótu powinien wystąpić dwa razy, a każdy inny klucz miał kolidować na mapie skrótów.
Ech ... Co?
Rozmiar kolekcji: 100. Limit skrótu: 90. Jeden na dziesięć kluczy ma zduplikowany kod skrótu.
Whoa Nelly! Jest to najbardziej prawdopodobny scenariusz, który koreluje z pytaniem pytającego i najwyraźniej początkowa pojemność 100 przy współczynniku obciążenia 1 jest tutaj jedną z najgorszych rzeczy! Przysięgam, że tego nie udawałem.
Wielkość kolekcji: 100. Limit mieszania: 100. Każdy klucz jako jego własny, unikalny kod skrótu. Nie oczekuje się kolizji.
To wygląda trochę spokojniej. W większości te same wyniki we wszystkich dziedzinach.
Wielkość kolekcji: 1000. Limit haszowania: 500. Podobnie jak w pierwszym teście, przeciążenie hashem wynosi od 2 do 1, ale teraz jest dużo więcej wpisów.
Wygląda na to, że każde ustawienie przyniesie tutaj przyzwoity wynik.
Wielkość kolekcji: 1000. Limit mieszania: 900. Oznacza to, że jeden na dziesięć kodów skrótów wystąpi dwukrotnie. Rozsądny scenariusz dotyczący kolizji.
I tak jak w przypadku putsów dla tej konfiguracji, otrzymujemy anomalię w dziwnym miejscu.
Wielkość kolekcji: 1000. Limit hash: 990. Trochę kolizji, ale tylko kilka. Całkiem realistyczne pod tym względem.
Przyzwoita wydajność wszędzie, z wyjątkiem połączenia dużej pojemności początkowej z niskim współczynnikiem obciążenia. Spodziewałbym się tego w przypadku putsów, ponieważ można się spodziewać dwóch zmian rozmiarów mapy skrótów. Ale dlaczego na początku?
Wielkość kolekcji: 1000. Limit mieszania: 1000. Brak zduplikowanych kodów skrótu, ale teraz z wielkością próbki 1000.
Całkowicie nie spektakularna wizualizacja. To wydaje się działać bez względu na wszystko.
Wielkość kolekcji: 100_000. Limit skrótu: 10_000. Przechodząc ponownie do 100K, z wieloma nakładającymi się kodami skrótu.
Nie wygląda ładnie, chociaż złe miejsca są bardzo zlokalizowane. Wydaje się, że tutaj wydajność w dużej mierze zależy od pewnej synergii między ustawieniami.
Wielkość kolekcji: 100_000. Limit skrótu: 90_000. Trochę bardziej realistyczne niż w poprzednim teście, tutaj mamy 10% przeciążenie w kodach hashujących.
Duża różnorodność, chociaż jeśli zmrużysz oczy, zobaczysz strzałkę skierowaną w prawy górny róg.
Wielkość kolekcji: 100_000. Limit skrótu: 99_000. To dobry scenariusz. Duża kolekcja z 1% przeciążeniem kodu skrótu.
Bardzo chaotyczny. Trudno tu znaleźć wiele struktur.
Wielkość kolekcji: 100_000. Limit skrótu: 100_000. Duży. Największa kolekcja z doskonałą funkcją skrótu.
Czy ktoś jeszcze myśli, że to zaczyna wyglądać jak grafika Atari? Wydaje się, że faworyzuje to początkową pojemność dokładnie taką, jak wielkość zbioru, -25% lub + 50%.
W porządku, teraz czas na wnioski ...
- Jeśli chodzi o czasy wstawiania: będziesz chciał uniknąć początkowej pojemności, która jest mniejsza niż spodziewana liczba wpisów na mapie. Jeśli wcześniej znana jest dokładna liczba, wydaje się, że ta liczba lub coś nieco powyżej wydaje się działać najlepiej. Wysokie współczynniki obciążenia mogą zrównoważyć mniejsze początkowe pojemności z powodu wcześniejszych zmian rozmiarów mapy skrótów. W przypadku wyższych pojemności początkowych nie wydają się one mieć większego znaczenia.
- Jeśli chodzi o czasy uzyskiwania: wyniki są tutaj nieco chaotyczne. Nie ma wiele do podsumowania. Wydaje się, że w dużym stopniu opiera się na subtelnych stosunkach między nakładaniem się kodu skrótu, początkową pojemnością i współczynnikiem obciążenia, przy czym niektóre rzekomo złe konfiguracje działają dobrze, a dobre działają okropnie.
- Najwyraźniej jestem pełen bzdur, jeśli chodzi o założenia dotyczące wydajności Javy. Prawda jest taka, że jeśli nie dostroisz perfekcyjnie swoich ustawień do implementacji
HashMap
, wyniki będą wszędzie. Jeśli jest jeszcze jedna rzecz, którą można z tego wyciągnąć, jest to, że domyślny rozmiar początkowy 16 jest nieco głupi dla wszystkiego innego niż najmniejsze mapy, więc użyj konstruktora, który ustawia rozmiar początkowy, jeśli masz jakiekolwiek pojęcie o kolejności rozmiaru To będzie.
- Tutaj mierzymy w nanosekundach. Najlepszy średni czas na 10 wejść to 1179 ns, a najgorszy 5105 ns na moim komputerze. Najlepszy średni czas na 10 otrzymów wyniósł 547 ns, a najgorszy 3484 ns. Może to być różnica czynnikowa 6, ale mówimy o mniej niż milisekundach. Na kolekcjach, które są znacznie większe niż to, co miał na myśli oryginalny plakat.
Cóż, to wszystko. Mam nadzieję, że mój kod nie zawiera jakiegoś strasznego przeoczenia, które unieważnia wszystko, co tutaj zamieściłem. Było to zabawne i nauczyłem się, że w końcu równie dobrze można polegać na Javie, aby wykonać swoją pracę, niż oczekiwać dużej różnicy po drobnych optymalizacjach. Nie oznacza to, że niektórych rzeczy nie należy unikać, ale wtedy mówimy głównie o konstruowaniu długich ciągów znaków w pętlach for, używaniu niewłaściwych struktur danych i tworzeniu algorytmu O (n ^ 3).