Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/string/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java String.intern()的性能惩罚_Java_String_Performance - Fatal编程技术网

Java String.intern()的性能惩罚

Java String.intern()的性能惩罚,java,string,performance,Java,String,Performance,很多人都在谈论String.intern()的性能优势,但实际上我更感兴趣的是性能损失可能是什么 我主要关注的是: 搜索成本:intern()计算常量池中是否存在internable字符串所需的时间。该成本如何与池中字符串的数量成比例 同步:显然,常量池是由整个JVM共享的。当从多个线程反复调用intern()时,该池的行为如何?它执行多少锁定?性能如何随争用扩展 我关心所有这些事情,因为我目前正在开发一个金融应用程序,由于重复的字符串,该应用程序存在使用太多内存的问题。一些字符串基本上看起

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

我主要关注的是:

  • 搜索成本:intern()计算常量池中是否存在internable字符串所需的时间。该成本如何与池中字符串的数量成比例
  • 同步:显然,常量池是由整个JVM共享的。当从多个线程反复调用intern()时,该池的行为如何?它执行多少锁定?性能如何随争用扩展
我关心所有这些事情,因为我目前正在开发一个金融应用程序,由于重复的字符串,该应用程序存在使用太多内存的问题。一些字符串基本上看起来像枚举值,并且只能有有限数量的潜在值(例如货币名称(“USD”、“EUR”))存在于超过一百万个副本中。在这种情况下,String.intern()似乎是不需要动脑筋的,但我担心每次在某处存储货币时调用intern()的同步开销


除此之外,一些其他类型的字符串可以有数百万个不同的值,但每个值仍有成千上万个副本(如ISIN代码)。对于这些,我担心实习一百万个字符串基本上会减慢intern()方法的速度,甚至使我的应用程序陷入困境。

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

我这样做是因为我正在处理一个有数百万个字符串的问题,许多字符串是相同的,我想(a)减少占用空间,(b)允许通过身份进行比较。对于我的问题,使用我的not
String.intern()
方法,有了实习总比没有实习好


YMMV.

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

您可以在上面找到基准代码(因为缺少更好的放置位置)。您还可以在源文件顶部的注释中找到我在启动JVM时使用的选项(以验证基准没有扭曲)

结果如下:

搜索成本(单线程) 图例

  • count:我们试图汇集的不同字符串的数量
  • 初始实习生:在字符串池中插入所有字符串所用的时间(毫秒)
  • lookup same string(查找相同字符串):使用与先前在池中输入的实例完全相同的实例,从池中再次查找每个字符串所用的时间(毫秒)
  • lookup equal string:从池中再次查找每个字符串所用的时间(毫秒),但使用不同的实例
String.intern()

ConcurrentHashMap.putIfAbsent()

搜索成本的结论是:String.intern()调用起来代价惊人。它的伸缩性非常差,大约为O(n),其中n是池中字符串的数量。当池中的字符串数量增加时,从池中查找一个字符串的时间会增加很多(对于10000个字符串,每次查找0.7微秒;对于1000个字符串,每次查找40微秒)

ConcurrentHashMap按预期缩放,池中的字符串数对查找速度没有影响


基于这个实验,我强烈建议,如果要实习多个字符串,就不要使用String.intern()。

我最近写了一篇关于在Java 6、7和8中实现String.intern()的文章:


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

以下微基准建议使用enum可以提供大约十倍的性能改进(通常的微基准警告适用)测试代码,如下所示:

public class Test {
   private enum E {
      E1;
      private static final Map<String, E> named = new HashMap<String, E>();
      static {
         for (E e : E.values()) {
            named.put( e.name(), e );
         }
      }

      private static E get(String s) {
         return named.get( s );
      }
   }

   public static void main(String... strings) {
      E e = E.get( "E1" ); // ensure map is initialised

      long start = System.nanoTime();
      testMap( 10000000 );
      long end = System.nanoTime();

      System.out.println( 1E-9 * (end - start) );
   }

   private static void testIntern(int num) {
      for (int i = 0; i < num; i++) {
         String s = "E1".intern();
      }
   }

   private static void testMap(int num) {
      for (int i = 0; i < num; i++) {
         E e = E.get( "E1" );
      }
   }
}
公共类测试{
私有枚举{
E1;
私有静态最终映射名为=new HashMap();
静止的{
对于(E:E.values()){
named.put(e.name(),e);
}
}
私有静态E get(字符串s){
返回名为.get(s);
}
}
公共静态void main(字符串…字符串){
E=E.get(“E1”);//确保映射已初始化
长启动=System.nanoTime();
testMap(10000000);
long end=System.nanoTime();
系统输出打印LN(1E-9*(结束-开始));
}
专用静态void testIntern(int num){
for(int i=0;i
结果(1000万次迭代): testIntern()-0.8秒 testMap()-0.06秒


当然是YMMV,但是枚举比字符串有很多好处…与其他随机字符串相比,类型安全性、添加方法的能力等等。看起来最好的方法是使用imho字符串。实习生变慢是因为两个原因:
1.-XX:StringTableSize限制。
在java中,它使用一个内部哈希表来管理字符串缓存,在java 6中,默认的StringTableSize值是1009,这意味着string.intern是O(string对象的数量/1009),当创建越来越多的string对象时,它的速度会变慢

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
public class Test {
   private enum E {
      E1;
      private static final Map<String, E> named = new HashMap<String, E>();
      static {
         for (E e : E.values()) {
            named.put( e.name(), e );
         }
      }

      private static E get(String s) {
         return named.get( s );
      }
   }

   public static void main(String... strings) {
      E e = E.get( "E1" ); // ensure map is initialised

      long start = System.nanoTime();
      testMap( 10000000 );
      long end = System.nanoTime();

      System.out.println( 1E-9 * (end - start) );
   }

   private static void testIntern(int num) {
      for (int i = 0; i < num; i++) {
         String s = "E1".intern();
      }
   }

   private static void testMap(int num) {
      for (int i = 0; i < num; i++) {
         E e = E.get( "E1" );
      }
   }
}
oop StringTable::intern(Handle string_or_null, jchar* name,  
                        int len, TRAPS) {  
  unsigned int hashValue = java_lang_String::hash_string(name, len);  
  int index = the_table()->hash_to_index(hashValue);  
  oop string = the_table()->lookup(index, name, len, hashValue);  
  // Found  
  if (string != NULL) return string;  
  // Otherwise, add to symbol to table  
  return the_table()->basic_add(index, string_or_null, name, len,  
                                hashValue, CHECK_NULL);  
}