Java String.intern()使用HashTable代替ConcurrentHashMap

时间:2019-05-18 23:03:01

标签: java performance concurrency

我正在研究String.intern(),此方法会降低性能。我已经将String.intern()与具有Microbenchmark的ConcurrentHashMap.putIfAbsent(s,s)进行了比较。使用过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实现。然后,阅读关于HashTable的javadoc,该文档显示:

  

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

这是非常令人困惑的情况。它建议使用ConcurrentHashMap,但使用HashTable会降低性能。有谁知道为什么要使用ConcurrentHashMap的本机HashTable实现实例?

1 个答案:

答案 0 :(得分:3)

这里发生了很多事情:

  1. 您的基准测试有很大的误差线。重复次数可能太少。这使得结果可疑

  2. 在每次运行 1 之后,您的基准测试似乎都没有重置“中间字符串”缓存。因此,这意味着高速缓存正在增长,并且每次重复都将从不同的条件开始。这可以解释错误栏...

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

    • 您的ConcurrentHashMap是一个巨大的内存泄漏。
    • 引用机制非常昂贵……在GC时代。

  

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

不。真正的原因是本机实现的行为有所不同:

  • 调用String::intern时可能会产生JNI调用开销。
  • 内部表示形式不同。
  • 它必须处理影响GC性能的参考。
  • 还有一些与字符串重复数据删除等有关的幕后交互。

请注意,这些事情在不同的Java版本之间有很大的差异。

  

这是非常令人困惑的情况。它建议使用ConcurrentHashMap,但使用HashTable会降低性能。

现在您正在谈论的是另一种情况,该情况与您的工作无关。

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

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

  

有人知道为什么要使用ConcurrentHashMap的本机HashTable实现实例吗?

它不使用哈希表;往上看。有许多原因导致它不是HashTableHashMapConcurrentHashMap

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

最后,请小心不要在此处关注错误的问题。如果由于实习生是应用程序的瓶颈而试图优化实习生,则另一种策略是根本不实习。实际上,它很少节省内存(特别是与G1GC的字符串重复数据删除相比),并且很少提高字符串处理性能。


总结:

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

1-在本机intern的情况下,我认为这是不可能的。