4

かなり大きな(200 MB)XMLファイルを解析しているところ、それぞれが一連のパラメーター(key = value)を定義するオブジェクトのツリーになります。このデータ構造はTomcatWebアプリケーションで実行されており、これらのパラメーターを検索するために使用されます。

数か月前、このサーバーでヒープメモリの問題を発見しました。パラメータのキーと値(ほとんどは非常に冗長です)をインターンすることで解決でき、メモリフットプリントが150MB以上からわずか20MBに削減されました。

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

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

別の解決策を支持してString.internを削除する価値があるかどうかを確認するために、誰かが数値を計算しましたか?

だから問題は?このような問題について、どうすれば競合をできるだけ少なくすることができますか?

ありがとう、ステファン

4

4 に答える 4

3

追加の間接ステップを追加します。キーを保持する2番目のHashMapを用意し、メモリ内の構造に挿入する前に、まずそこでキーを検索します。これにより、String#intern()よりもはるかに柔軟性が高くなります。

ただし、Tomcatを起動するたびに200MBのXMLファイルを解析する必要があり、余分な10秒で人々が不平を言う場合(Tomcatを頻繁に再起動しますか?)、フラグがポップアップします(データベース、Apacheでさえも使用することを検討しましたか?)ダービー、解析されたデータを保持するには?)

于 2011-07-28T10:06:15.010 に答える
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

代わりに、配列を文字列プールとして使用します。

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()でより高速なフォームが必要な場合は、固定サイズの配列を使用できます。

于 2011-07-28T12:00:53.410 に答える
0

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

数回のテスト実行の後、解析中とNameの実装中の両方で、最終的にchar配列を処理するソリューションになりました。

String.toCharArray()を使用して文字列の配列を取得するか、String.charAt(pos)を使用できます。配列間の迅速なコピーには、System.arrayCopyを使用しました。

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

于 2011-07-28T10:11:36.187 に答える
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と同じ概念に従って、最初のユーザーペナルティを節約しますが、別のビルドステップが追加され、構成ファイルが変更された場合はデプロイメントになります(とにかく制御する必要があります)。

于 2011-11-02T05:42:28.000 に答える