提问者:小点点

JavaString. intern()使用HashTable而不是并发HashMap


我正在研究String. intern(),这个方法有一个性能损失。我已经将String.intern()与Concurrent tHashMap.putIfAbsend(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));
    }
}

结果符合预期。搜索字符串时,Concurrent tHashMap比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()比ConloctHashMap慢,因为String.intern()是原生HashTable实现。然后,阅读有关HashTable的javadoc,该文档说:

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

这是一个非常令人困惑的情况。它推荐ConloctHashMap,但它使用HashTable,尽管性能下降。有人知道为什么使用本地HashTable植入ConloctHashMap实例吗?


共1个答案

匿名用户

这里发生了许多事情:

>

  • 您的基准测试有非常大的误差条。重复计数可能太小。这使得结果值得怀疑。

    每次运行1后,您的基准测试似乎并没有重置“实习字符串”缓存。这意味着缓存正在增长,每次重复都将从不同的条件开始。这可以解释错误条…

    您的ConCurrentHashMap在功能上不等同于String::intern。后者使用与引用对象等效的本机对象来确保可以垃圾收集被拘留的字符串。您的ConCurrentHashMap实现没有。为什么这很重要?

    • 你的并发HashMap是一个巨大的内存泄漏。
    • 引用机制是昂贵的…GC。(尽管可能比内存泄漏更便宜。)

    String. intern()比并发HashMap慢,因为String.intern()是原生HashTable实现。

    不。真正的原因是本机实现的做法不同:

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

    请注意,这些东西在不同的Java版本中差异很大。

    这是非常令人困惑的情况。它建议使用并发HashMap,但它使用HashTable虽然性能下降。

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

    >

  • 注意String::实习生不使用HashTableHashMap;见上文。

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

    有没有人知道为什么使用并发HashMap的本机HashTable实现实例?

    它不使用哈希表;见上文。它没有HashTableHashMapConCurrentHashMap的原因有很多:

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

    最后,要小心你没有关注这里的错误问题。如果你试图优化实习生,因为这是你应用程序的瓶颈,另一个策略是完全不实习。在实践中,它很少节省内存(尤其是与G1GC的字符串去重相比),也很少提高字符串处理性能。

    综上所述:

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

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