【问题标题】:Java String.intern() use HashTable instead of ConcurrentHashMapJava String.intern() 使用 HashTable 而不是 ConcurrentHashMap
【发布时间】:2019-05-18 23:03:01
【问题描述】:

我正在研究 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,这个文档说:

如果不需要线程安全的实现,建议使用 HashMap 代替 Hashtable。如果需要线程安全的高并发实现,建议使用 ConcurrentHashMap 代替 Hashtable。

这是非常混乱的情况。它推荐 ConcurrentHashMap,但它使用 HashTable 虽然性能损失。有谁知道为什么使用 ConcurrentHashMap 的本机 HashTable 实现实例?

【问题讨论】:

  • String.intern 使用 hash table(一个结构),但不是 java.util.Hashtable 类。

标签: java performance concurrency


【解决方案1】:

这里发生了很多事情:

  1. 您的基准测试有很大的误差线。重复计数可能太小了。这使得结果有问题

  2. 看起来您的基准测试并未在每次运行后重置“内部字符串”缓存1。这意味着缓存正在增长,并且每次重复都将从不同的条件开始。这可以解释错误栏...

  3. 您的ConcurrentHashMap 在功能上不等同于String::intern。后者使用与Reference 对象等效的本机来确保可以对实习字符串进行垃圾回收。您的 ConcurrentHashMap 实现没有。为什么这很重要?

    • 您的ConcurrentHashMap 存在大量内存泄漏。
    • 引用机制很昂贵……在 GC 时。 (虽然可能比内存泄漏更便宜2。)

String.intern() 比 ConcurrentHashMap 慢,因为 String.intern() 是本机 HashTable 实现。

没有。真正的原因是原生实现的做法不同:

  • 内部表示不同。本机 (intern) 字符串池使用以本机代码实现的自定义哈希表。
  • 它必须处理影响 GC 性能的引用。
  • 还有与字符串重复数据删除和其他事情的幕后交互。

请注意,这些内容在不同的 Java 版本之间存在很大差异。

这是一个非常混乱的情况。它推荐 ConcurrentHashMap,但它使用 HashTable 虽然性能损失。

现在你在谈论一个不同的场景,它与你正在做的事情无关。

  • 注意String::intern 既不使用HashTable 也不使用HashMap;见上文。

  • 您找到的引用是关于如何从哈希表中获得良好的并发性能。您的基准是(AFAIK)单线程。对于串行用例,HashMap 将提供比其他用例更好的性能。

有没有人知道为什么使用ConcurrentHashMap 的原生HashTable 实现实例?

它不使用哈希表;往上看。它没有HashTableHashMapConcurrentHashMap的原因有很多:

  • 是它更加注重内存利用率。所有 Java 哈希表实现都需要大量内存,这使得它们不适合通用字符串实习。
  • 使用 Reference 类的内存和 CPU 开销很大。
  • 计算新创建的长度为 N 的字符串的哈希值是 O(N),这在对可能有数百/数千个字符长的字符串进行实习时非常重要。

最后,请注意不要在这里关注错误的问题。如果您因为实习是应用程序的瓶颈而尝试优化实习,则另一种策略是根本不实习。在实践中,它很少节省内存(尤其是与 G1GC 的字符串去重相比),也很少提高字符串处理性能。


总结:

  • 您正在比较苹果和橙子。您的基于地图的实施并不等同于原生实习。
  • String::intern 不仅(甚至主要)针对速度进行优化。
  • 通过关注速度,您忽略了内存利用率......以及内存利用率对速度的次要影响。
  • 考虑根本不实习的潜在优化。

1 - 在原生 intern 的情况下,我认为这是不可能的。
2 - 常规堆中的 Java 内存泄漏会影响长期 GC 性能,因为保留的对象需要被 GC 反复标记和复制。也可能有次要影响。

【讨论】:

    猜你喜欢
    • 2012-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-27
    • 2016-08-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多