String.intern()的性能损失

Lor*_*igs 41 java string performance

很多人都在谈论String.intern()的性能优势,但实际上我对性能损失可能更感兴趣.

我主要担心的是:

  • 搜索成本:intern()用于确定常量字符串是否存在于常量池中的时间.该成本如何随着该池中的字符串数量而扩展?
  • 同步:显然,整个JVM共享常量池.当从多个线程一遍又一遍地调用intern()时,该池如何表现?它执行了多少锁定?性能如何与争用一起扩展?

我关注所有这些事情,因为我目前正在对使用具有复制,因为字符串太多内存的问题,一个金融应用程序.某些字符串基本上看起来像枚举值,并且只能存在数量有限的潜在值(例如货币名称("USD","EUR"))超过一百万份.在这种情况下,String.intern()似乎是不费吹灰之力,但我担心每次在某处存储货币时调用intern()的同步开销.

最重要的是,一些其他类型的字符串可以具有数百万个不同的值,但每个字符串仍然有数万个副本(例如ISIN代码).对于这些,我担心实习百万串基本上会减慢实习生()方法这么多,以拖垮我的应用程序.

Lor*_*igs 39

我自己做了一些基准测试.对于搜索成本部分,我决定将String.intern()与ConcurrentHashMap.putIfAbsent(s,s)进行比较.基本上,这两个方法做同样的事情,除了String.intern()是一个本机方法,它存储和读取直接在JVM中管理的SymbolTable,而ConcurrentHashMap.putIfAbsent()只是一个普通的实例方法.

你可以在github gist上找到基准代码(因为缺少一个更好的地方).您还可以在源文件顶部的注释中找到我在启动JVM时使用的选项(以验证基准不会偏差).

无论如何这里是结果:

搜索成本(单线程)

传说

  • count:我们尝试汇集的不同字符串的数量
  • initial intern:在字符串池中插入所有字符串所用的时间(以毫秒为单位)
  • 查找相同的字符串:使用与先前在池中输入的完全相同的实例,从池中再次查找每个字符串所用的时间(以毫秒为单位)
  • lookup equal string:从池中再次查找每个字符串所花费的时间(以ms为单位),但使用不同的实例

中的String.intern()

count       initial intern   lookup same string  lookup equal string
1'000'000            40206                34698                35000
  400'000             5198                 4481                 4477
  200'000              955                  828                  803
  100'000              234                  215                  220
   80'000              110                   94                   99
   40'000               52                   30                   32
   20'000               20                   10                   13
   10'000                7                    5                    7
Run Code Online (Sandbox Code Playgroud)

ConcurrentHashMap.putIfAbsent()

count       initial intern   lookup same string  lookup equal string
1'000'000              411                  246                  309
  800'000              352                  194                  229
  400'000              162                   95                  114
  200'000               78                   50                   55
  100'000               41                   28                   28
   80'000               31                   23                   22
   40'000               20                   14                   16
   20'000               12                    6                    7
   10'000                9                    5                    3
Run Code Online (Sandbox Code Playgroud)

搜索成本的结论:String.intern()的调用成本高得惊人.它在O(n)中非常严重,其中n是池中字符串的数量.当池中的字符串数量增加时,从池中查找一个字符串的时间量会增长得更多(每次查找0.7微秒,10'000个字符串,每次查找40微秒,1'000'000个字符串).

ConcurrentHashMap按预期进行扩展,池中的字符串数量对查找速度没有影响.

根据这个实验,我强烈建议如果要实习多个字符串,请避免使用String.intern().

  • 您是否尝试使用不同大小的字符串池表进行基准测试:http://java-performance.info/string-intern-in-java-6-7-8/ (2认同)

小智 21

我最近在Java 6,7和8中编写了一篇关于String.intern()实现的文章:Java 6,7和8中的 String.intern - 字符串池.

有一个-XX:StringTableSize JVM参数,它允许你使String.intern在Java7 +中非常有用.所以,不幸的是,我不得不说这个问题目前正在向读者提供误导性的信息.

  • 因此,为了让其他读者清楚,您的文章解释了String.intern池基本上是一个HashSet,其底层HashMap具有固定数量的存储桶,由VM命令行选项决定.但是,HashMap有点退化,不知道如何增加它包含的桶数.这就是为什么String.intern对于几个字符串执行接近O(1)的原因,但是对于更多字符串,它会降级为O(n). (5认同)

bma*_*ies 5

我发现最好使用fastutil哈希表并自己进行实习而不是重用String.intern()。使用我自己的哈希表意味着我可以对并发做出自己的决定,并且我不争夺PermGen空间。

我之所以这样做,是因为我正在研究一个问题,该问题实际上有数百万个字符串,许多相同,并且我想(a)减少占用空间,(b)允许按标识进行比较。对于我的问题,使用我的notString.intern()方法,实习比没有好。

YMMV。

  • 这并没有真正回答这个问题。 (2认同)