Оптимизация памяти Java Collectors.toMap

GetVersion и GetVersionEx заменялись вспомогательными функциями для различных версий . Вы хотите IsWindows10OrGreater . Они могут быть найдены в VersionHelpers.h .

IsWindows10OrGreater доступен только в последней версии SDK / Visual Studio 2015. Вы можете использовать IsWindowsVersionOrGreater в общем случае. Например, в моем ящике 7 я получаю TRUE для IsWindowsVersionOrGreater (6, 0, 0) .

Помните, что параметры, которые эта функция принимает, относятся к номеру сборки Windows и НЕму маркетинговому имени. Так что Windows 8 - это сборка 6.2. Windows 7 - 6.0 и т. Д.

10
задан Justin Borromeo 13 July 2018 в 22:59
поделиться

3 ответа

Вторая реализация будет включать в себя множественные изменения размера HashMap.

Я определил это, просто запустив его в отладчике и разбив его каждый раз при изменении размера хэш-карты. Сначала я изменил код, который вы опубликовали, чтобы скомпилировать его в моей системе:

import java.util.*;
import java.util.stream.*;

class Test {
  public static void main(String[] args) {
    List<Object> list = new ArrayList<Object>();
    for(int i=0; i<100000; i++) {
      list.add(new Integer(i));
    }
    new Test().listToMap(list);
  }

    Map<Integer, Object> listToMap(List<Object> objs) {
        return objs.stream().collect(Collectors.toMap(Object::hashCode, obj -> obj));
    }
}

Затем я скомпилировал его и запустил в отладчике, пока не нажмет listToMap:

$ javac Test.java && jdb Test
Initializing jdb ...
> stop in Test.listToMap
Deferring breakpoint Test.listToMap.
It will be set after the class is loaded.
> run
run Test
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
>
VM Started: Set deferred breakpoint Test.listToMap

Breakpoint hit: "thread=main", Test.listToMap(), line=14 bci=0
14            return objs.stream().collect(Collectors.toMap(Object::hashCode, obj -> obj));

main[1]

Затем я установил точку останова в java.util.HashMap.resize и продолжил:

main[1] stop in java.util.HashMap.resize
Set breakpoint java.util.HashMap.resize
main[1] cont
>
Breakpoint hit: "thread=main", java.util.HashMap.resize(), line=678 bci=0

main[1]

и cont появился еще до тех пор, пока мне не стало скучно:

main[1] cont
>
Breakpoint hit: "thread=main", java.util.HashMap.resize(), line=678 bci=0

main[1] cont
>
Breakpoint hit: "thread=main", java.util.HashMap.resize(), line=678 bci=0

main[1] cont
>
Breakpoint hit: "thread=main", java.util.HashMap.resize(), line=678 bci=0

main[1] cont
>
Breakpoint hit: "thread=main", java.util.HashMap.resize(), line=678 bci=0

main[1] cont
>
Breakpoint hit: "thread=main", java.util.HashMap.resize(), 
line=678 bci=0

main[1] print size
 size = 3073
main[1] cont
>
Breakpoint hit: "thread=main", java.util.HashMap.resize(), line=678 bci=0

main[1] print size
 size = 6145
main[1] cont
>
Breakpoint hit: "thread=main", java.util.HashMap.resize(), line=678 bci=0

main[1] print size
 size = 12289

Итак, да: it наиболее определенно сохраняет изменения размера снова и снова.

8
ответ дан that other guy 17 August 2018 в 12:06
поделиться

Будет ли вторая реализация включать несколько изменений размера HashMap или оптимизирована для выделения достаточно памяти?

В вашем коде первый. См. https://stackoverflow.com/a/51333961/139985

Стоит отметить, что для вашей текущей реализации:

  1. Большая часть дополнительная память, потребляемая путем изменения размера, будет восстановлена ​​при следующем запуске GC.
  2. После окончания collect вы все равно можете получить основной массив хэшей, который в два раза больше. Память «впустую» может составлять до 8 байтов на запись в таблице, но в среднем она будет 4 байта на запись.
  3. Тем не менее, узлы ввода хешей будут самым большим потребителем памяти в a HashMap. Каждая запись потребляет примерно 32 байта ... в дополнение к пространству, используемому для представления ключа и значения.

(Приведенные выше числа предполагают 64-битные ссылки.)


В качестве альтернативы, если вы используете перегрузку аргумента 4 в toMap(), вы можете предоставить Supplier для создания Map для заполнения. Это позволяет сделать следующее:

  • Выделить HashMap с начальной мощностью, достаточной для того, чтобы избежать изменения размера, но не слишком большого.
  • Используйте (гипотетический) альтернативная реализация Map, которая использует меньше памяти для каждой записи, чем HashMap.
  • Создайте оболочку, чтобы заполнить объект, похожий на карту, который не реализует Map<K,V> ... для вашего K и V. (Например, вы могли бы использовать TLongObjectHashMap в библиотеке GNU Trove.)

(В последних двух случаях целью является найти Map или «map-like «класс, который использует меньше памяти (для ваших типов K и V), но все же имеет соответствующую производительность для поиска.)

7
ответ дан Stephen C 17 August 2018 в 12:06
поделиться

Подводя итог тому, что говорили все остальные и добавив немного, вот способ сделать это, используя пользовательский Collector. Однако вы должны иметь в виду две вещи:

  1. Продолжая мысль о Stephen C из его ответа , вы действительно не должны быть обеспокоены оптимизацией таких случаев, пока вы не узнаете, что это действительно узкое место в вашей заявке. Как сказал Дональд Кнут , «преждевременная оптимизация - это корень всего зла».
  2. Поскольку shmosel указал в комментарии, Collector, что выделяет HashMap с предопределенным размером, будет перераспределять, если указанный Collector используется в параллельном режиме. Поэтому Collector, который я предлагаю, не поддерживает параллельный сбор.

Сказав это, вы можете записать следующий общий Collector s:

public class ExtraCollectors {

    public static <T, K, V> Collector<T, ?, HashMap<K, V>> toSizedMap(
            Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends V> valueMapper, int size) {
        return toSequentialMap(
                () -> com.google.common.collect.Maps.newHashMapWithExpectedSize(size),
                keyMapper, valueMapper, Collector.Characteristics.UNORDERED
        );
    }

    public static <T, K, V, M extends Map<K, V>> Collector<T, ?, M> toSequentialMap(
            Supplier<M> mapSupplier, Function<? super T, ? extends K> keyMapper,
            Function<? super T, ? extends V> valueMapper, Collector.Characteristics... characteristics) {
        return Collector.of(
                mapSupplier,
                (map, element) -> map.merge(
                        keyMapper.apply(element), valueMapper.apply(element), ExtraCollectors::mergeUnsupported
                ),
                ExtraCollectors::combineUnsupported,
                characteristics
        );
    }

    private static <T> T mergeUnsupported(T valueA, T valueB) {
        throw new UnsupportedOperationException("This Collector does not support merging.");
    }

    private static <A> A combineUnsupported(A accumulatorA, A accumulatorB) {
        throw new UnsupportedOperationException("This Collector does not support parallel streams.");
    }
}

Обратите внимание, что я использовал Guava Maps.newHashMapWithExpectedSize , чтобы вы получили HashMap с нужным вам размером (примерно то, что Andreas объяснил в своем комментарии к вашему вопросу ). Если у вас нет зависимости от Guava (и вы не хотите этого делать), вы можете просто скопировать метод Maps.capacity на вашу кодовую базу.

Используя метод ExtraCollectors.toSizedMap(), определенный выше, ваш метод преобразования будет выглядеть следующим образом:

Map<Long, KeyedObject> listToMap(List<? extends KeyedObject> objs) {
    return objs.stream().collect(ExtraCollectors.toSizedMap(KeyedObject::getKey, obj -> obj, objs.size()));

}

Тем не менее, если вам действительно нужна максимальная производительность (по цене повторного использования), вы можете пропустить API Stream, и примените ваше решение 1, но с Maps.newHashMapWithExpectedSize, чтобы получить размер HashMap.

1
ответ дан Tomasz Linkowski 17 August 2018 в 12:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: