2011-07-28 2 views
4

かなり大きな(200 MB)XMLファイルを解析していて、それぞれが一連のパラメータ(key = value)を定義するオブジェクトのツリーになっています。このデータ構造はTomcat Webアプリケーションで実行され、これらのパラメータを検索するために使用されます。string.intern()の競合を避け、メモリフットプリントを低く抑えるにはどうすればよいですか?

数か月前に、このサーバーでヒープメモリの問題が検出されました。パラメータのキーと値(ほとんどが非常に冗長)をインターナショナルにすることで、メモリのフットプリントを150 MB以上から20 MBに縮小することで解決できました。

今日、人々は起動時間について不平を言っているので、私はサーバを再訪しています。私はサーバーにプロファイリングしており、XPP3でXMLを解析するには40秒かかります。ここでString.intern()は30秒以上かかります。

これはトレードオフです。私は自分自身でインターンをすることができることを知っています。 XMLを解析すると、単純にHashMapがその仕事をする可能性があるため、シングルスレッドになります。しかし、あなたは知っている、これは奇妙な感じです。

String.internを別のソリューションに置き換える価値があるかどうかを誰かが気にしていませんでしたか?

質問は?どのように私はそのような問題のためにできるだけ低い競合を得ることができますか?

おかげで、 ステファン

+0

使用しているJavaのバージョンはどれですか?最新バージョンは、char []の代わりにbyte []が使用される圧縮された文字列をサポートしています。 –

+0

現在、私たちはJava 6のほぼ最新バージョンを使用しています。 しかし、私は今朝、これらの文字列をインターンに入れないと300 MB以上のコストがかかることに気付きました。時間の経過とともに、このデータ構造は大きく成長しました。 –

+0

あなたの文字列がeden空間からコピーされると、可能であればbyte []に​​変換されます。これは大きな文字列の半分のサイズになります。 32ビットJVMを使用してメモリ使用量を最小限に抑えていると仮定します。 –

答えて

3

は、余分な間接ステップを追加します。キーを保持して二HashMapを持っており、メモリ内の構造物にそれらを挿入する前に、最初にそこの鍵を探します。これにより、String#intern()よりもはるかに柔軟性が向上します。

しかし、200MBのXMLファイルをすべてのTomcat起動時に解析する必要がある場合、余分な10秒で人が不満を募らせます(彼らは毎回Tomcatを再起動しますか?) - フラグをポップアップしますパースされたデータを保持するためにApache Derbyを使用していますか?)。

+0

+1、xmlファイルの解析済みデータは、ファイルの最終更新日とともにキャッシュする必要があります。 – SirDarius

+0

埋め込みDerby DBの効率性については疑問に思っていました。クエリは、単にメモリ内のデータ構造を調べるのではなく、一部のSQLを解析する問題です。とにかく、あなたは正しいです:これは私たちがしばらく考えているものですが、確かにもっと努力しています。 –

+0

私は地図に行くだけです。最近、FastHashMapがまだ高速ではないが、地図のように見えるのは、この百万人のインターンにはほとんど時間がかかりません(1秒未満)。ありがとう! しかし、私はなぜString.intern()が非常に非効率であるのか不思議です。おそらく、そのような高いスループットのために作られたものではないでしょうか? –

0

文字列が検証された「名前」オブジェクトに解析されているという問題がありました。 これはアプリケーションのあらゆる場所で実行され、メモリと速度の両方で最適化する必要がありました。

いくつかのテストを実行した後、私たちは最終的に解析と名前の実装の両方で、char配列を処理するソリューションを完成させました。

String.toCharArray()文字列の配列を取得するか、String.charAt(pos)を使用できます。配列間を素早くコピーするために、System.arrayCopyを使用しました。

実際には、ルックアップにキャッシュを使用するよりも解析が高速でした。

+1

'String.toCharArray()'は内部配列へのアクセスを提供しません**。それが呼び出されるたびに新しいコピー。内部配列は直接アクセス可能ではなく、複数の 'String'インスタンス間で共有することができます。 –

+0

私は悪い、それを削除します。 –

+0

これは本当にこのような「シンプルな」問題の巨大な最適化です。複雑さを増やす前にDerby DBの提案を最初に試してみると思います。それはすでに複雑すぎます... –

1

String.intern()は、文字列を追加するほどうまくスケールされないようです。それはプール内の文字列の数でO(n)に現れます。

Random rand = new Random(); 
for(int i=0;i<100;i++) { 
    long start = System.nanoTime(); 
    for(int j=0;j<100000;j++) 
     Long.toString(rand.nextLong()).toString().intern(); 
    long time = System.nanoTime() - start; 
    System.out.printf("Took %,d ns on average to intern() a random string%n", time/100000); 
} 

プリント

Took 1,586 ns on average to intern() a random string 
Took 3,843 ns on average to intern() a random string 
Took 7,551 ns on average to intern() a random string 
Took 13,436 ns on average to intern() a random string 
Took 20,226 ns on average to intern() a random string 
Took 27,609 ns on average to intern() a random string 
Took 35,098 ns on average to intern() a random string 
Took 42,439 ns on average to intern() a random string 
Took 50,801 ns on average to intern() a random string 
Took 20,975 ns on average to intern() a random string 
Took 4,634 ns on average to intern() a random string 
Took 10,512 ns on average to intern() a random string 
Took 16,914 ns on average to intern() a random string 
Took 23,601 ns on average to intern() a random string 
Took 30,230 ns on average to intern() a random string 
Took 36,184 ns on average to intern() a random string 
Took 43,266 ns on average to intern() a random string 

代わりIは、文字列プールとしてアレイを使用します。

private static void testHashArray(String[] strings2, int size) { 
    String[] pool = new String[size]; 
    int hit=0, miss=0; 
    long start2 = System.nanoTime(); 
    for (String s : strings2) { 
     int hash = (s.hashCode() & 0x7fffffff) % pool.length; 
     String s2 = pool[hash]; 
     if (s.equals(s2)) { 
      hit++; 
     } else { 
      miss++; 
     } 
     if (s2 != s) 
      pool[hash] = s; 
    } 
    long time2 = System.nanoTime() - start2; 
    System.out.printf("Hash size: %,d took %.3f second. Hit/miss %,d/%,d %n", size, time2/1e9, hit, miss); 
} 

public static void main(String... args) { 
    Random rand = new Random(); 

    // a million unique strings. 
    String[] strings = new String[1000 * 1000]; 
    for (int i = 0; i < strings.length; i++) 
     strings[i] = String.valueOf(rand.nextLong()); 
    // random selection of Strings 
    String[] strings2 = new String[10 * 1000 * 1000]; 
    int totalSize = 0; 
    for (int i = 0; i < strings2.length; i++) { 
     int idx = (int) Math.pow(strings.length, rand.nextFloat()); 
     String s = strings[idx]; 
     strings2[i] = s; 
     totalSize += s.length() + 16; // with overhead 
    } 
    System.out.printf("Original size %,d%n", totalSize); 

    Set<String> uniqueStrings = Collections.newSetFromMap(new IdentityHashMap<String, Boolean>()); 
    uniqueStrings.addAll(Arrays.asList(strings2)); 
    System.out.printf("Unique strings %,d%n", uniqueStrings.size()); 

    long start = System.nanoTime(); 
    HashMap<String,String> map = new HashMap(); 
    for(String s: strings2) 
     map.put(s,s); 
    long time = System.nanoTime() - start; 
    System.out.printf("Took %.3f second to map strings%n", time/1e9); 

    testHashArray(strings2, 10192); 
    testHashArray(strings2, 101929); 
    testHashArray(strings2, 1019291); 
} 

プリント

Original size 353,293,201 
Unique strings 766,222 
Took 0.979 second to map strings 
Hash size: 10,192 took 0.357 second. Hit/miss 5,213,210/4,786,790 
Hash size: 101,929 took 0.309 second. Hit/miss 7,202,094/2,797,906 
Hash size: 1,019,291 took 0.254 second. Hit/miss 8,789,382/1,210,618 

インターンをしていることはどのようにバックグラウンドスレッドでロードした後、それを実行については、遅い場合。サーバがロードされた後、重複が見つかると文字列をintern()することができます。

実際に130MBを保存する必要がありますか?私はそれが素晴らしいと思うけど、メモリはとにかく何かのために使用されるだろうか?

intern()でフォームを高速化したい場合は、固定サイズの配列を使用できます。

+0

内部データ構造は、スニペットが引かれているシステムから何らかの種類のサイトマップを探し出しているものです。それが始動時にそこにあったらそれはいいと思う。 I/Oがボトルネックではないため、数か月前に、以前は並列に処理することを考えました。だから提案に感謝! –

+0

分析していただきありがとうございます(後で追加したようですね) - とにかく、このような便利な機能のために非常に悲しい性能があります。私は今地図に行くよ。良いところは、後で地図を投げることができることです。それは時間を節約し、メモリフットプリントも良好です。 –

+0

私のマシンでは、アレイを使用するのがマップより3〜4倍高速でした。 ;) –

0

これは別の考えですが、クッキー側で少し音がするかもしれません。あなただけの自分のXMLファイルを解析し、実際の文字列を使用してマップを移入するJavaコードを吐き出すコードジェネレータ(これらは、コンパイル時にインターンます)この

public final class ConfigurationData { 
    public static String get(String key) { 
    return map.get(key); 
    } 
    private static final Map<String,String> MAP; 
    static { 
    MAP = new HashMap<String,String>([[[ number of records to load up ]]]); 
    MAP.put([[[key 1]]], [[[ value 1 ]]]); 
    MAP.put([[[key 2]]], [[[ value 2 ]]]); 
    ... 
    } 
} 

このような

何かを書くことを考えていますプリコンパイルされたJSPと同じコンセプトに従い、最初のユーザーペナルティを節約しますが、別のビルドステップが追加され、構成ファイルの変更があった場合には展開されます(これは制御する必要があります)。

+0

これは実験的にはうれしいことですが、私が選んだのは最も実用的なアプローチです。 XMLファイルは、アプリケーションが実行され、コンテンツが変更されている限り、何度も解析されます。だからあなたの提案は、実行中のTomcat webapp :-)のオンザフライクラス読み込みで余分な努力を払うでしょう –

関連する問題