использование локального потока, чтобы избежать создания карты, чтобы уменьшить G C, но не удалось - PullRequest
2 голосов
/ 18 февраля 2020

Чтобы избежать онлайн G C проблем.

Фон

Исходная карта будет скопирована в поток (содержащийся в пуле потоков), и в этом потоке скопированная карта может быть обновлена ​​и после обновления некоторые скопированные карты могут возвращаться к исходной карте .

Опытные соображения

В онлайн-условиях есть два типичных условия по сравнению с моей локальной книгой ma c:

  • гораздо более эффективные серверы (процессор, память и IO)
  • высокая пропускная способность (QPS на миллионном уровне)

Размер информации для origMap может быть: 50 ключей, и каждое значение составляет около 50 символов.

Текущее решение

Сейчас я использую ThreadLocal для создания ReusableMap, чтобы гарантировать, что каждая карта связана с потоком, и когда требуется копия и карта в потоке уже создана, мы можем напрямую использовать карту.

Конечно, сначала нам нужно очистить карту и скопировать содержимое с исходной карты .

Я думал, что это уменьшит G C, но когда я запускаю некоторый тест с использованием jmh и отслеживаю результат в Visual G C через jvisualvm ; К сожалению, я обнаружил, что это не так, как я ожидал. Тем не менее, есть много GC, как и раньше.

Обновлено 2020-02-21

Во-первых, действительно благодаря помощи @Holger и @GotoFinal я пробовал другие варианты с моим ограниченным пониманием. Но пока, все так плохо, ничего не работает с моим локальным тестом.

Ничего полезного не получится, и я думаю, что попробую что-то другое, чтобы глубже изучить технологию оптимизации и кэширования JVM.

Просто для справки, тесты, которые я выполнил следующим образом:

  1. настройка размера карты в ключевом аспекте, размера значения в аспекте длины;
  2. использование простого огромного l oop путем удаления jmh для устранения скрывающихся влияний;
  3. использовать несколько карт (вместо одной - поскольку есть сценарии ios, нам нужно передать несколько);
  4. вносит ограничительные изменения в дочерний поток, чтобы поддерживать более высокое повторное использование в аспекте входа и узла при использовании
    cacheMap.putAll(origMap)
    cacheMap.keySet().retainAll(origMap.keySet())
    
  5. запускать тесты дольше от 10 минут до 2 ч 30 м;

Некоторый код, демонстрирующий то, что я только что упомянул:

public class ReusableHashMapTwoCopy {
    private static final String DEFAULT_MAP_KEY = "defaultMap";
    /**
     * weak or soft reference perhaps could be used: https://stackoverflow.com/a/299702/2361308
     * <p>
     * via the static ThreadLocal initialized, each thread will only see the value it set itself;
     */
    private static ThreadLocal<Map> theCache = new ThreadLocal<>();

    /**
     * the default usage when there is only one map passed from parent
     * thread to child thread.
     *
     * @param origMap the parent map
     * @param <K>     generic type for the key
     * @param <V>     generic type for the value
     * @return a map used within the child thread - the reusable map
     */
    public static <K, V> Map<K, V> getMap(Map<K, V> origMap) {
        return getMap(DEFAULT_MAP_KEY, origMap);
    }


    public static <K, V> Map<K, V> getMap() {
        return getMap(DEFAULT_MAP_KEY);
    }

    /**
     * clone the parent-thread map at the beginning of the child thread,
     * after which you can use the map as usual while it's thread-localized;
     * <p>
     * no extra map is created for the thread any more - preventing us from creating
     * map instance all the time.
     *
     * @param theMapKey the unique key to specify the map to be passed into the child thread;
     * @param origMap   the parent map
     * @param <K>       generic type for the key
     * @param <V>       generic type for the value
     * @return the cached map reused within the child thread
     */
    public static <K, V> Map<K, V> getMap(String theMapKey, Map<K, V> origMap) {
        Map<String, Map<K, V>> threadCache = theCache.get();
        if (Objects.isNull(threadCache)) {
//            System.out.println("## creating thread cache");
            threadCache = new HashMap<>();
            theCache.set(threadCache);
        } else {
//            System.out.println("**## reusing thread cache");
        }
        Map<K, V> cacheMap = threadCache.get(theMapKey);
        if (Objects.isNull(cacheMap)) {
//            System.out.println("  ## creating thread map cache for " + theMapKey);
            cacheMap = new HashMap<>();
        } else {
//            System.out.println("  **## reusing thread map cache for " + theMapKey);
            cacheMap.clear();
        }
        if (MapUtils.isNotEmpty(origMap)) {
            cacheMap.putAll(origMap);
            cacheMap.keySet().retainAll(origMap.keySet());
        }
        threadCache.put(theMapKey, cacheMap);
        return cacheMap;
    }

    public static <K, V> Map<K, V> getMap(String theMapKey) {
        return getMap(theMapKey, null);
    }




    public static void main(String[] args) throws Exception {
        org.openjdk.jmh.Main.main(args);
//        print(MyState.parentMapMedium_0);
//        print(MyState.parentMapSmall_0);
    }

    private static void blackhole(Object o) {

    }


    @Benchmark
    @Fork(value = 1, warmups = 0, jvmArgs = {"-Xms50M", "-Xmx50M"})
    @Warmup(iterations = 1, time = 5)
    @Timeout(time = 3, timeUnit = TimeUnit.HOURS)
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.MINUTES)
    @Measurement(iterations = 1, time = 150, timeUnit = TimeUnit.MINUTES)
    public void testMethod() throws Exception {
        final Map<String, String> theParentMap0 = MyState.parentMapSmall_0;
        final Map<String, String> theParentMap1 = MyState.parentMapSmall_1;
//        final Map<String, String> theParentMap0 = MyState.parentMapMedium_0;
//        final Map<String, String> theParentMap1 = MyState.parentMapMedium_1;
        ThreadUtils.getTheSharedPool().submit(() -> {
            try {
                Map<String, String> theChildMap0 = new HashMap<>(theParentMap0);
                theChildMap0.put("test0", "child");

                Map<String, String> theChildMap1 = new HashMap<>(theParentMap1);
                theChildMap1.put("test1", "child");

                for (int j = 0; j < 1_0; ++j) {
                    blackhole(theChildMap0);
                    blackhole(theChildMap1);
                    sleep(10);
                }
            } catch (Exception e) {
                System.err.println(e.getMessage());
            }
        }).get();
    }


    private static void print(Object o) {
        print(o, "");
    }

    private static void print(Object o, String content) {
        String s = String
                .format("%s: current thread: %s map: %s", content, Thread.currentThread().getName(), toJson(o));
        System.out.println(s);
    }

    @State(Scope.Benchmark)
    public static class MyState {
        // 20 & 100 -> 2.5k
        static Map<String, String> parentMapSmall_0 = generateAMap(20, 100);
        static Map<String, String> parentMapSmall_1 = generateAMap(20, 100);
        // 200 & 200 -> 45k
        static Map<String, String> parentMapMedium_0 = generateAMap(200, 200);
        static Map<String, String> parentMapMedium_1 = generateAMap(200, 200);
    }

    private static Map<String, String> generateAMap(int size, int lenLimit) {
        Map<String, String> res = new HashMap<>();
        String aKey = "key - ";
        String aValue = "value - ";
        for (int i = 0; i < size; ++i) {
            aKey = i + " - " + LocalDateTime.now().toString();
            aValue = i + " - " + LocalDateTime.now().toString() + aValue;
            res.put(aKey.substring(0, Math.min(aKey.length(), lenLimit)),
                    aValue.substring(0, Math.min(aValue.length(), lenLimit)));
        }
        return res;
    }
}

1 Ответ

1 голос
/ 18 февраля 2020

HashMap содержит массив объектов Node внутри, и если вы вызываете hashMap.clear(), этот массив очищается, поэтому все объекты Node теперь доступны для сборки мусора.
Так что кеширование такой карты вообще не поможет.

Если вы хотите ограничить G C, может быть, вы можете просто использовать ConcurrentHashMap? Если вам нужно разделить работу между потоками, вы можете просто передать список / массив ключей каждому потоку, с которым они должны работать, и вернуть список обновленных значений. Трудно сказать больше без точного описания проблемы, которую вы пытаетесь решить.

Но сначала вы также должны подумать, если вам это действительно нужно, вам действительно нужно много * *2020* подчеркнуть, чтобы получить реальные проблемы с G C, которая не может быть решена с помощью простой настройки, если вы предоставите некоторый нормальный объем памяти для процесса java.

Другое решение, предложенное Хольгером:
Вместо использования cacheMap.clear() просто сделайте:

cacheMap.putAll(origMap)
cacheMap.keySet().retainAll(origMap.keySet())

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

cacheMap.replaceAll((key, value) -> null);

Тогда все ключи все еще присутствуют в карте, но значения равны нулю , поэтому при использовании карты вы можете просто игнорировать нулевые значения. Вы также можете использовать какой-либо нулевой объект, если это необходимо / возможно.

...