我正在研究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实现实例?
答案 0 :(得分:3)
这里发生了很多事情:
您的基准测试有很大的误差线。重复次数可能太少。这使得结果可疑。
在每次运行 1 之后,您的基准测试似乎都没有重置“中间字符串”缓存。因此,这意味着高速缓存正在增长,并且每次重复都将从不同的条件开始。这可以解释错误栏...
您的ConcurrentHashMap
在功能上不等同于String::intern
。后者使用与Reference
对象等效的本机,以确保可以对垃圾回收的字符串进行垃圾收集。您的ConcurrentHashMap
实现没有。为什么这很重要?
ConcurrentHashMap
是一个巨大的内存泄漏。 String.intern()比ConcurrentHashMap慢,因为String.intern()是本机HashTable实现。
不。真正的原因是本机实现的行为有所不同:
String::intern
时可能会产生JNI调用开销。请注意,这些事情在不同的Java版本之间有很大的差异。
这是非常令人困惑的情况。它建议使用ConcurrentHashMap,但使用HashTable会降低性能。
现在您正在谈论的是另一种情况,该情况与您的工作无关。
请注意,String::intern
既不使用HashTable
也不使用HashMap
;见上文。
您找到的报价是关于如何从哈希表中获得良好的并发性能。您的基准是(AFAIK)单线程。对于串行使用案例,HashMap
将提供比其他情况更好的性能。
有人知道为什么要使用ConcurrentHashMap的本机HashTable实现实例吗?
它不使用哈希表;往上看。有许多原因导致它不是HashTable
或HashMap
或ConcurrentHashMap
:
Reference
类的内存和CPU开销很大。最后,请小心不要在此处关注错误的问题。如果由于实习生是应用程序的瓶颈而试图优化实习生,则另一种策略是根本不实习。实际上,它很少节省内存(特别是与G1GC的字符串重复数据删除相比),并且很少提高字符串处理性能。
总结:
String::intern
并非仅仅(甚至主要)针对速度进行了优化。 1-在本机intern
的情况下,我认为这是不可能的。