String.intern()のパフォーマンス上の利点について多くの人が話しますが、実際には、パフォーマンスのペナルティが何であるかについてもっと興味があります。
私の主な関心事は次のとおりです。
私は現在、文字列が重複しているためにメモリを使いすぎるという問題を抱えている金融アプリケーションに取り組んでいるため、これらすべてのことを心配しています。一部の文字列は基本的に列挙値のように見え、100万を超えるコピーに存在する可能性のある値(通貨名( "USD"、 "EUR")など)の数は限られています。この場合、String.intern()は簡単に思えますが、通貨をどこかに格納するたびにintern()を呼び出すことによる同期のオーバーヘッドが心配です。
その上、他のタイプの文字列には数百万の異なる値がありますが、それでもそれぞれのコピーが数万あります(ISINコードなど)。これらの場合、100万文字列をインターンすると、基本的にintern()メソッドの速度が低下し、アプリケーションが停止するのではないかと心配しています。
私は自分でベンチマークを少し行いました。検索コストの部分については、String.intern()をConcurrentHashMap.putIfAbsent(s、s)と比較することにしました。基本的に、これら2つのメソッドは同じことを行いますが、String.intern()はJVMで直接管理されるSymbolTableを格納および読み取るネイティブメソッドであり、ConcurrentHashMap.putIfAbsent()は単なる通常のインスタンスメソッドです。
ベンチマークコードは github Gist にあります(配置するのに適した場所がないため)。また、JVMの起動時に(ベンチマークが歪んでいないことを確認するために)使用したオプションは、ソースファイルの上部にあるコメントにあります。
とにかくここに結果があります:
凡例
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
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
検索コストの結論:String.intern()は、呼び出すのに驚くほどコストがかかります。 O(n)ここで、nはプール内の文字列の数です。プール内の文字列の数が増えると、1つの文字列を検索する時間になります。プールからの増加ははるかに大きくなります(10'000文字列のルックアップあたり0.7マイクロ秒、1'000'000文字列のルックアップあたり40マイクロ秒)。
ConcurrentHashMapは期待どおりにスケーリングします。プール内の文字列の数は、ルックアップの速度に影響を与えません。
この実験に基づいて、複数の文字列をインターンする場合は、String.intern()の使用を避けることを強くお勧めします。
私は最近、String.intern()の実装に関する記事をJava 6、7、および8で作成しました: String.intern in Java 6、 7および8-文字列プーリング 。
-XX:StringTableSize JVMパラメーターがあります。これにより、String.internをJava7 +で非常に便利にすることができます。したがって、残念ながら、この質問は現在、読者に誤解を招く情報を提供していると言わざるを得ません。
String.intern()
を再利用するよりも、fastutilハッシュテーブルを使用して独自のインターンを行う方がよいことがわかりました。自分のハッシュテーブルを使用するということは、並行性について自分で決定できることを意味し、PermGenスペースをめぐって競合することはありません。
これを行ったのは、いわば何百万もの文字列があり、多くが同一である問題に取り組んでいたためです。(a)フットプリントを削減し、(b)IDによる比較を可能にしたかったのです。私の問題では、notString.intern()
アプローチを使用して、インターンを使用しないよりもインターンを使用した方がうまくいきました。
YMMV。
String.internが遅くなるのは、次の2つの理由によるものです。
1。 -XX:StringTableSizeの制限。
Javaでは、内部ハッシュテーブルを使用して文字列キャッシュを管理します。Java 6、デフォルトのStringTableSize値は1009です。つまり、string.internはO(文字列の数)です。 object/1009)、ますます多くの文字列オブジェクトが作成されると、速度が低下します。
\ openjdk7\hotspot\src\share\vm\classfile\symbolTable.cpp
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);
}
2. Java 6では、文字列キャッシュプールはヒープではなくパーマ領域にあります。ほとんどの場合、パーマサイズは比較的小さく設定します。