Ответ 1
Хорошо, чтобы положить эту вещь в состояние покоя, я создал тестовое приложение, чтобы запустить несколько сценариев и получить визуализацию результатов. Вот как выполняются тесты:
- Было опробовано несколько разных размеров сбора: сто, одна тысяча и сто тысяч записей.
- Используемые ключи - это экземпляры класса, которые однозначно идентифицируются идентификатором. Каждый тест использует уникальные ключи с добавлением целых чисел в виде идентификаторов. Метод
equals
использует только идентификатор, поэтому никакое сопоставление ключей не перезаписывает другой.
- Ключи получают хеш-код, который состоит из остатка модуля от их идентификатора к некоторому предварительно установленному номеру. Мы будем называть это число хэш-предел. Это позволило мне контролировать количество столкновений хэшей, которые ожидались. Например, если наш размер коллекции равен 100, у нас будут ключи с идентификаторами от 0 до 99. Если предел хэша 100, каждый ключ будет иметь уникальный хэш-код. Если хэш-предел равен 50, ключ 0 будет иметь тот же хэш-код, что и ключ 50, 1 будет иметь тот же хеш-код, что и 51. Другими словами, ожидаемое количество столкновений хэша на ключ - это размер коллекции, деленный на хэш предел.
- Для каждой комбинации размера коллекции и хэш-лимита я запускаю тест с использованием хеш-карт, инициализированных различными настройками. Эти параметры являются коэффициентом загрузки и начальной мощностью, которая выражается в качестве коэффициента настройки коллекции. Например, тест с размером коллекции 100 и начальным коэффициентом емкости 1,25 инициализирует хэш-карту с начальной емкостью 125.
- Значение для каждого ключа - это просто новый
Object
.
- Каждый результат теста инкапсулируется в экземпляр класса Result. В конце всех тестов результаты упорядочены от худшей общей производительности до лучших.
- Среднее время для puts и gets рассчитывается на 10 puts/gets.
- Все тестовые комбинации запускаются один раз, чтобы исключить влияние компиляции JIT. После этого тесты выполняются для фактических результатов.
Здесь класс:
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 {
//First entry of each array is the sample collection size, subsequent entries
//are the hash limits
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};
//Doing a warmup run to eliminate JIT influence
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();
//Now for the real thing...
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();
}
// ResultVisualizer.visualizeResults(results);
}
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 + "%");
//Generating our sample key collection.
final List<Key> keys = generateSamples(hashLimit, sampleSize);
//Generating our value collection
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);
//Haha, what kind of noob explicitly calls for garbage collection?
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) {
//Equals implies same hashCode if limit is the same
//Same hashCode doesn't necessarily implies equals
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.");
}
}
}
Запуск этого может занять некоторое время. Результаты распечатываются по стандарту. Вы могли заметить, что я прокомментировал строку. Эта строка вызывает визуализатор, который выводит визуальные представления результатов в png файлы. Класс для этого приведен ниже. Если вы хотите запустить его, раскомментируйте соответствующую строку в приведенном выше коде. Будьте осторожны: класс визуализатора предполагает, что вы работаете в Windows и создаете папки и файлы в C:\temp. При работе на другой платформе отрегулируйте это.
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) {
//[x][y] => x is mapped to initial capacity, y is mapped to load factor
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);
}
}
Визуализированный вывод выглядит следующим образом:
- Тесты делятся сначала на размер коллекции, затем на лимит хешей.
- Для каждого теста есть выходное изображение относительно среднего времени старта (на 10 статов) и среднее время получения (по 10 получает). Изображения представляют собой двумерные "карты тепла", которые показывают цвет за комбинацию начальной емкости и коэффициента загрузки.
- Цвета в изображениях основаны на среднем времени в нормализованном масштабе от наилучшего до наихудшего результата, от насыщенного зеленого до насыщенного красного. Другими словами, лучшее время будет полностью зеленым, а худшее время будет полностью красным. Два разных измерения времени никогда не должны иметь один и тот же цвет.
- Цветовые карты рассчитываются отдельно для puts и gets, но охватывают все тесты для соответствующих категорий.
- Визуализации показывают начальную емкость по оси x и коэффициент нагрузки по оси y.
Без дальнейших церемоний взгляните на результаты. Я начну с результатов для puts.
Положите результаты
Размер коллекции: 100. Предел хэша: 50. Это означает, что каждый хеш-код должен появляться дважды, а каждый другой ключ сталкивается в хэш-карте.
![size_100_hlimit_50_puts]()
Ну, это не начинается очень хорошо. Мы видим, что есть большая точка доступа для начальной емкости на 25% выше размера коллекции с коэффициентом загрузки 1. Нижний левый угол не работает слишком хорошо.
Размер коллекции: 100. Предел хэша: 90. Один из десяти ключей имеет повторяющийся хеш-код.
![size_100_hlimit_90_puts]()
Это немного более реалистичный сценарий, не имеющий идеальной хэш-функции, но все же 10% перегрузки. Горячая точка ушла, но комбинация низкой начальной емкости с низким коэффициентом нагрузки, очевидно, не работает.
Размер коллекции: 100. Предел хэша: 100. Каждый ключ является его собственным уникальным хэш-кодом. Никаких коллизий не ожидается, если есть достаточное количество ведер.
![size_100_hlimit_100_puts]()
Первоначальная емкость 100 с коэффициентом загрузки 1 кажется прекрасной. Удивительно, но более высокая начальная емкость с более низким коэффициентом нагрузки не всегда хороша.
Размер коллекции: 1000. Хэш-предел: 500. Здесь становится все более серьезным, с 1000 записей. Как и в первом тесте, есть хеш-перегрузка от 2 до 1.
![size_1000_hlimit_500_puts]()
В левом нижнем углу все еще плохо. Но, похоже, существует симметрия между комбинированным коэффициентом нижнего начального счета/высокой нагрузки и более высоким коэффициентом начального счета/низкой нагрузки.
Размер коллекции: 1000. Предел хеширования: 900. Это означает, что один из десяти хэш-кодов будет повторяться дважды. Разумный сценарий, касающийся столкновений.
![size_1000_hlimit_900_puts]()
Там что-то очень смешное происходит с маловероятной комбинацией начальной мощности, которая слишком мала с коэффициентом нагрузки выше 1, что довольно противоречиво. В противном случае, все еще довольно симметричный.
Размер коллекции: 1000. Предел хэша: 990. Некоторые столкновения, но только несколько. В этом отношении вполне реалистично.
![size_1000_hlimit_990_puts]()
У нас симпатичная симметрия. Нижний левый угол по-прежнему не оптимален, но коэффициент загрузки 1000 единиц мощности /1,0 и коэффициент нагрузки 1250/0,75 нагрузки находятся на одном уровне.
Размер коллекции: 1000. Предел хэша: 1000. Нет повторяющихся хеш-кодов, но теперь с размером выборки 1000.
![size_1000_hlimit_1000_puts]()
Нечего здесь сказать. Сочетание более высокой начальной емкости с коэффициентом нагрузки 0,75, по-видимому, несколько превосходит комбинацию 1000 начальных мощностей с коэффициентом нагрузки 1.
Размер коллекции: 100_000. Предел хеширования: 10_000. Хорошо, теперь это становится серьезным, с размером выборки сто тысяч и 100 хэш-кодов для каждого ключа.
![size_100000_hlimit_10000_puts]()
Хлоп! Я думаю, мы нашли наш более низкий спектр. Емкость инициализации точно размера коллекции с коэффициентом загрузки 1 делает здесь очень хорошо, но не так, как во всем магазине.
Размер коллекции: 100_000. Предел хеширования: 90_000. Чуть более реалистичным, чем предыдущий тест, здесь мы имеем 10% -ную перегрузку в хэш-кодах.
![size_100000_hlimit_90000_puts]()
Левый нижний угол по-прежнему нежелателен. Более высокие начальные мощности работают лучше всего.
Размер коллекции: 100_000. Предел хеширования: 99_000. Хороший сценарий, это. Большая коллекция с перегрузкой хеш-кода 1%.
![size_100000_hlimit_99000_puts]()
Используя точный размер коллекции как емкость init с коэффициентом загрузки 1 выигрывает здесь! Однако немного большие возможности инициализации работают довольно хорошо.
Размер коллекции: 100_000. Предел хэша: 100_000. Большая. Самая большая коллекция с идеальной хэш-функцией.
![size_100000_hlimit_100000_puts]()
Некоторые удивительные вещи здесь. Начальная емкость с 50% дополнительной комнатой при коэффициенте нагрузки 1 побед.
Хорошо, что это для puts. Теперь мы проверим все. Помните, что приведенные ниже карты относятся к лучшим/худшим временам получения, время старта больше не учитывается.
Получить результаты
Размер коллекции: 100. Предел хэша: 50. Это означает, что каждый хеш-код должен появляться дважды, и каждый другой ключ должен был столкнуться на карте хэша.
<Т411 >
Эх... Что?
Размер коллекции: 100. Предел хэша: 90. Один из десяти ключей имеет повторяющийся хеш-код.
![size_100_hlimit_90_gets]()
Эй, Нелли! Это наиболее вероятный сценарий, который коррелирует с вопросом опроса, и, по-видимому, первоначальная емкость 100 с коэффициентом загрузки 1 - это одна из худших вещей здесь! Клянусь, я не подделал это.
Размер коллекции: 100. Предел хэша: 100. Каждый ключ является его собственным уникальным хэш-кодом. Коллизий не ожидается.
![size_100_hlimit_100_gets]()
Это выглядит немного спокойнее. В основном те же результаты по всем направлениям.
Размер коллекции: 1000. Предел хэша: 500. Как и в первом тесте, есть хеш-перегрузка от 2 до 1, но теперь с гораздо большим количеством записей.
![size_1000_hlimit_500_gets]()
Похоже, что любая настройка принесет достойный результат.
Размер коллекции: 1000. Предел хеширования: 900. Это означает, что один из десяти хэш-кодов будет повторяться дважды. Разумный сценарий, касающийся столкновений.
![size_1000_hlimit_900_gets]()
И так же, как с помощью puts для этой настройки, мы получаем аномалию в странном месте.
Размер коллекции: 1000. Предел хэша: 990. Некоторые столкновения, но только несколько. В этом отношении вполне реалистично.
![size_1000_hlimit_990_gets]()
Достойная производительность везде, за исключением сочетания высокой начальной емкости с низким коэффициентом нагрузки. Я ожидал бы этого для puts, так как можно было бы ожидать изменения размеров двух хэш-карт. Но почему на получение?
Размер коллекции: 1000. Предел хэша: 1000. Нет повторяющихся хеш-кодов, но теперь с размером выборки 1000.
![size_1000_hlimit_1000_gets]()
Совершенно непредсказуемая визуализация. Кажется, что это работает независимо от того, что.
Размер коллекции: 100_000. Предел хеширования: 10_000. Переход в 100K снова, с большим количеством хеш-кода перекрывается.
![size_100000_hlimit_10000_gets]()
Это не выглядит красиво, хотя плохие пятна очень локализованы. Производительность здесь, по-видимому, во многом зависит от определенной синергии между настройками.
Размер коллекции: 100_000. Предел хеширования: 90_000. Чуть более реалистичным, чем предыдущий тест, здесь мы имеем 10% -ную перегрузку в хэш-кодах.
![size_100000_hlimit_90000_gets]()
Большая дисперсия, хотя если вы щуритесь, вы увидите стрелку, указывающую на верхний правый угол.
Размер коллекции: 100_000. Предел хеширования: 99_000. Хороший сценарий, это. Большая коллекция с перегрузкой хеш-кода 1%.
![size_100000_hlimit_99000_gets]()
Очень хаотично. Трудно найти здесь много структуры.
Размер коллекции: 100_000. Предел хэша: 100_000. Большая. Самая большая коллекция с идеальной хэш-функцией.
![size_100000_hlimit_100000_gets]()
Кто-нибудь думает, что это начинает выглядеть как графика Atari? Это, по-видимому, способствует первоначальной емкости именно размера коллекции, -25% или +50%.
Хорошо, настало время сделать выводы...
- Что касается времени выполнения: вы хотите избежать первоначальных мощностей, которые ниже ожидаемого количества записей в карте. Если точное число известно заранее, это число или что-то немного выше, кажется, работает лучше всего. Высокие коэффициенты нагрузки могут компенсировать более низкие начальные мощности из-за более раннего изменения карты хэш-карты. Для более высоких начальных мощностей они, похоже, не так важны.
- Что касается получения времени: результаты здесь немного хаотичны. Там нечего заключить. Похоже, что он очень сильно полагается на тонкие соотношения между перекрытием хеш-кода, начальной емкостью и коэффициентом нагрузки, причем некоторые, предположительно, плохие настройки работают хорошо, а хорошие настройки работают ужасно.
- Я, по-видимому, полна дерьма, когда речь заходит о предположениях о производительности Java. По правде говоря, если вы не настроите свои настройки на реализацию
HashMap
, результаты будут повсюду. Если есть одна вещь, чтобы отнять у него это, то первоначальный размер по умолчанию 16 немного тупой для чего угодно, кроме самых маленьких карт, поэтому используйте конструктор, который устанавливает начальный размер, если у вас есть какая-то идея о том, какой порядок размера это будет.
- Мы измеряем в наносекундах здесь. Лучшее среднее время на 10 статов было 1179 нс и худшее 5105 нс на моей машине. Лучшее среднее время на 10 получает 547 нс и самое худшее 3484 нс. Это может быть разницей в 6 раз, но мы говорим меньше миллисекунды. На коллекциях, которые намного больше, чем то, что имел в виду оригинальный плакат.
Хорошо, что это. Надеюсь, у моего кода нет какого-то ужасающего надзора, который отменяет все, что я здесь разместил. Это было весело, и я узнал, что в конце концов вы можете просто полагаться на Java, чтобы выполнять свою работу, а не рассчитывать на большую разницу между крошечными оптимизациями. Это не означает, что некоторые вещи не следует избегать, но тогда мы в основном говорим о построении длинных строк для циклов, использовании неправильных структур данных и создании алгоритма O (n ^ 3).
Ответ 3
Просто пойдите с 101
. Я на самом деле не уверен, что это необходимо, но это не могло стоить усилий, чтобы когда-либо побеспокоиться о том, чтобы узнать наверняка.
... просто добавьте 1
.
РЕДАКТИРОВАТЬ: Некоторые обоснования для моего ответа.
Во-первых, я предполагаю, что ваш HashMap
не будет превышать 100
; если это так, вы должны оставить фактор нагрузки таким, какой он есть. Аналогичным образом, если ваша проблема связана с производительностью, оставьте коэффициент загрузки таким, какой он есть. Если ваша проблема связана с памятью, вы можете сохранить ее, установив статический размер. Возможно, это стоит сделать, если вы забиваете много материала в памяти; т.е. хранят множество карт или создают карты размером с кучу пространства.
Во-вторых, я выбираю значение 101
, потому что оно обеспечивает лучшую читабельность... если я потом посмотрю на ваш код и вижу, что вы установили начальную емкость 100
, и вы загружаете ее 100
, мне нужно будет прочитать Javadoc, чтобы убедиться, что он не изменит размер, когда он достигнет точно 100
. Конечно, я не найду ответа там, поэтому мне придется посмотреть на источник. Это не стоит... просто оставьте его 101
, и все счастливы, и никто не ищет исходный код java.util.HashMap
. Hoorah.
В-третьих, утверждение о том, что установка HashMap
в точную емкость того, что вы ожидаете с коэффициентом загрузки 1
", убьет ваш поиск и производительность вставки" просто не соответствует действительности, даже если он сделан жирным шрифтом.
... если у вас есть n
ведра, и вы произвольно назначаете n
элементы в n
ведра, да, вы будете в конечном итоге с элементами в том же ведро, конечно... но это а не конец света... на практике это всего лишь пара сравнений сравнений. На самом деле, там особенно. Небольшая разница, если вы считаете, что альтернатива назначает n
элементы в n/0.75
ведра.
Не нужно зачитывать мое слово...
Быстрый тестовый код:
static Random r = new Random();
public static void main(String[] args){
int[] tests = {100, 1000, 10000};
int runs = 5000;
float lf_sta = 1f;
float lf_dyn = 0.75f;
for(int t:tests){
System.err.println("=======Test Put "+t+"");
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
long norm_put = testInserts(map, t, runs);
System.err.print("Norm put:"+norm_put+" ms. ");
int cap_sta = t;
map = new HashMap<Integer,Integer>(cap_sta, lf_sta);
long sta_put = testInserts(map, t, runs);
System.err.print("Static put:"+sta_put+" ms. ");
int cap_dyn = (int)Math.ceil((float)t/lf_dyn);
map = new HashMap<Integer,Integer>(cap_dyn, lf_dyn);
long dyn_put = testInserts(map, t, runs);
System.err.println("Dynamic put:"+dyn_put+" ms. ");
}
for(int t:tests){
System.err.println("=======Test Get (hits) "+t+"");
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
fill(map, t);
long norm_get_hits = testGetHits(map, t, runs);
System.err.print("Norm get (hits):"+norm_get_hits+" ms. ");
int cap_sta = t;
map = new HashMap<Integer,Integer>(cap_sta, lf_sta);
fill(map, t);
long sta_get_hits = testGetHits(map, t, runs);
System.err.print("Static get (hits):"+sta_get_hits+" ms. ");
int cap_dyn = (int)Math.ceil((float)t/lf_dyn);
map = new HashMap<Integer,Integer>(cap_dyn, lf_dyn);
fill(map, t);
long dyn_get_hits = testGetHits(map, t, runs);
System.err.println("Dynamic get (hits):"+dyn_get_hits+" ms. ");
}
for(int t:tests){
System.err.println("=======Test Get (Rand) "+t+"");
HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
fill(map, t);
long norm_get_rand = testGetRand(map, t, runs);
System.err.print("Norm get (rand):"+norm_get_rand+" ms. ");
int cap_sta = t;
map = new HashMap<Integer,Integer>(cap_sta, lf_sta);
fill(map, t);
long sta_get_rand = testGetRand(map, t, runs);
System.err.print("Static get (rand):"+sta_get_rand+" ms. ");
int cap_dyn = (int)Math.ceil((float)t/lf_dyn);
map = new HashMap<Integer,Integer>(cap_dyn, lf_dyn);
fill(map, t);
long dyn_get_rand = testGetRand(map, t, runs);
System.err.println("Dynamic get (rand):"+dyn_get_rand+" ms. ");
}
}
public static long testInserts(HashMap<Integer,Integer> map, int test, int runs){
long b4 = System.currentTimeMillis();
for(int i=0; i<runs; i++){
fill(map, test);
map.clear();
}
return System.currentTimeMillis()-b4;
}
public static void fill(HashMap<Integer,Integer> map, int test){
for(int j=0; j<test; j++){
if(map.put(r.nextInt(), j)!=null){
j--;
}
}
}
public static long testGetHits(HashMap<Integer,Integer> map, int test, int runs){
long b4 = System.currentTimeMillis();
ArrayList<Integer> keys = new ArrayList<Integer>();
keys.addAll(map.keySet());
for(int i=0; i<runs; i++){
for(int j=0; j<test; j++){
keys.get(r.nextInt(keys.size()));
}
}
return System.currentTimeMillis()-b4;
}
public static long testGetRand(HashMap<Integer,Integer> map, int test, int runs){
long b4 = System.currentTimeMillis();
for(int i=0; i<runs; i++){
for(int j=0; j<test; j++){
map.get(r.nextInt());
}
}
return System.currentTimeMillis()-b4;
}
Результаты тестирования:
=======Test Put 100
Norm put:78 ms. Static put:78 ms. Dynamic put:62 ms.
=======Test Put 1000
Norm put:764 ms. Static put:763 ms. Dynamic put:748 ms.
=======Test Put 10000
Norm put:12921 ms. Static put:12889 ms. Dynamic put:12873 ms.
=======Test Get (hits) 100
Norm get (hits):47 ms. Static get (hits):31 ms. Dynamic get (hits):32 ms.
=======Test Get (hits) 1000
Norm get (hits):327 ms. Static get (hits):328 ms. Dynamic get (hits):343 ms.
=======Test Get (hits) 10000
Norm get (hits):3304 ms. Static get (hits):3366 ms. Dynamic get (hits):3413 ms.
=======Test Get (Rand) 100
Norm get (rand):63 ms. Static get (rand):46 ms. Dynamic get (rand):47 ms.
=======Test Get (Rand) 1000
Norm get (rand):483 ms. Static get (rand):499 ms. Dynamic get (rand):483 ms.
=======Test Get (Rand) 10000
Norm get (rand):5190 ms. Static get (rand):5362 ms. Dynamic get (rand):5236 ms.
re: & uarr; — там об этом → || & larr; большая разница между различными настройками.
Что касается моего первоначального ответа (бит выше первой горизонтальной линии), он был преднамеренно glib, потому что в большинстве случаев этот тип микрооптимизации не является хорошим.