Java String.intern() использует HashTable вместо ConcurrentHashMap

Я исследую String.intern(), и этот метод снижает производительность. Я сравнил String.intern() с ConcurrentHashMap.putIfAbsent(s,s) с Microbenchmark. Используется Java1.8.0_212, Ubuntu 18.04.2 LTS

@Param({"1", "100", "10000", "1000000"})
private int size;

private StringIntern stringIntern;
private ConcurrentHashMapIntern concurrentHashMapIntern;

@Setup
public void setup(){
    stringIntern = new StringIntern();
    concurrentHashMapIntern = new ConcurrentHashMapIntern();
}
public static class StringIntern{
    public String intern(String s){
        return s.intern();
    }
}
public static class ConcurrentHashMapIntern{
    private final Map<String, String> map;

    public ConcurrentHashMapIntern(){
        map= new ConcurrentHashMap<>();
    }
    public String intern(String s){
        String existString = map.putIfAbsent(s, s);
        return (existString == null) ? s : existString;
    }
}

@Benchmark
public void intern(Blackhole blackhole){
    for(int count =0; count<size; count ++){
        blackhole.consume(stringIntern.intern("Example "+count));
    }
}
@Benchmark
public void concurrentHashMapIntern(Blackhole blackhole){
    for(int count =0; count<size; count++){
        blackhole.consume(concurrentHashMapIntern.intern("Example " +count));
    }
}

Результат ожидаемый. ConcurrentHashMap быстрее, чем String.intern() при поиске строки.

Benchmark                             (size)  Mode  Cnt        Score        Error  Units
MyBenchmark.concurrentHashMapIntern        1  avgt    5        0.056 ±      0.007  us/op
MyBenchmark.concurrentHashMapIntern      100  avgt    5        6.094 ±      2.359  us/op
MyBenchmark.concurrentHashMapIntern    10000  avgt    5      787.802 ±    264.179  us/op
MyBenchmark.concurrentHashMapIntern  1000000  avgt    5   136504.010 ±  17872.866  us/op
MyBenchmark.intern                         1  avgt    5        0.129 ±      0.007  us/op
MyBenchmark.intern                       100  avgt    5       13.700 ±      2.404  us/op
MyBenchmark.intern                     10000  avgt    5     1618.514 ±    460.563  us/op
MyBenchmark.intern                   1000000  avgt    5  1027915.854 ± 638910.023  us/op

String.intern() медленнее, чем ConcurrentHashMap, потому что String.intern() является собственной реализацией HashTable. А затем прочитайте javadoc о HashTable, в этой документации говорится:

If a thread-safe implementation is not needed, it is recommended to use HashMap in place of Hashtable. If a thread-safe highly-concurrent implementation is desired, then it is recommended to use ConcurrentHashMap in place of Hashtable.

Это очень запутанная ситуация. Он рекомендует ConcurrentHashMap, но использует HashTable, несмотря на снижение производительности. Кто-нибудь знает, почему используется собственный экземпляр реализации HashTable для ConcurrentHashMap?

String.intern использует хеш-таблица (структуру), но не java.util.Hashtable класс.
apangin 19.05.2019 02:07
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
1
305
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Здесь происходит ряд вещей:

  1. Ваши тесты имеют очень большие полосы погрешностей. Количество повторов, вероятно, слишком мало. Это делает результаты под вопросом.

  2. Не похоже, чтобы ваши тесты сбрасывали кеши «интернированных строк» ​​после каждого запуска1. Это означает, что кэши растут, и каждое повторение будет начинаться с разных условий. Это может объяснить полосы ошибок ...

  3. Ваш ConcurrentHashMap функционально не эквивалентен String::intern. Последний использует собственный эквивалент объектов Reference, чтобы гарантировать, что интернированные строки могут быть удалены сборщиком мусора. Ваша реализация ConcurrentHashMap не работает. Почему это важно?

    • Ваш ConcurrentHashMap — это массовая утечка памяти.
    • Ссылочный механизм стоит дорого... во время GC. (Хотя, возможно, дешевле2, чем утечка памяти.)

String.intern() slower than ConcurrentHashMap because String.intern() is native HashTable implementation.

Нет. Настоящая причина в том, что нативная реализация работает по-другому:

  • Внутренние представления разные. Собственный (intern) пул строк использует пользовательскую хеш-таблицу, реализованную в собственном коде.
  • Он должен обрабатывать ссылки, которые влияют на производительность GC.
  • Есть также закулисные взаимодействия с дедупликацией строк и другими вещами.

Обратите внимание, что эти вещи значительно различаются в разных версиях Java.

This is very confusing situation. It recommend ConcurrentHashMap, but it using HashTable although performance penalty.

Теперь вы говорите о другом сценарии, который не имеет отношения к тому, что вы делаете.

  • Обратите внимание, что String::intern не использует ни HashTable, ни HashMap; см. выше.

  • Цитата, которую вы нашли, о том, как получить хорошую производительность одновременный из хеш-таблицы. Ваш тест (AFAIK) однопоточный. Для последовательного варианта использования HashMap даст лучшую производительность, чем другие.

Does anyone have any idea about why used native HashTable implementation instance of ConcurrentHashMap ?

Он не использует хеш-таблицу; см. выше. Есть ряд причин, по которым это не HashTable или HashMap или ConcurrentHashMap:

  • Это то, что он уделяет больше внимания использованию памяти. Все реализации хэш-таблиц Java имеют тип жаждущий памяти, что делает их непригодными для интернирования строк общего назначения.
  • Накладные расходы памяти и ЦП при использовании классов Reference значительны.
  • Вычисление хэша вновь созданной строки длины N равно O(N), что будет важно при интернировании строк, длина которых может составлять сотни/тысячи символов.

Наконец, будьте осторожны, чтобы не сосредоточиться не на той проблеме. Если вы пытаетесь оптимизировать стажировку, потому что она является узким местом в вашем приложении, другая стратегия — вообще не стажироваться. На практике это редко экономило память (особенно по сравнению с дедупликацией строк в G1GC) и редко улучшало производительность обработки строк.


В итоге:

  • Вы сравниваете яблоки и апельсины. Ваша реализация на основе карты не эквивалентна нативной стажировке.
  • String::intern не оптимизирован исключительно (даже в первую очередь) для скорости.
  • Сосредоточив внимание на скорости, вы игнорируете использование памяти... и вторичный эффект использования памяти на скорость.
  • Рассмотрим потенциальную оптимизацию отказа от интернирования вообще.

1 - And in the native intern case, I don't think that is possible.
2 - A Java memory leak in the regular heap impacts on long-term GC performance because the retained objects need to be repeatedly marked and copied by the GC. There may be secondary effects too.

Другие вопросы по теме