うならぼ

どうも。

MinecraftでG1GCを使ったメモ

2GBの Incremental CMS でぼちぼち動いていて、6GBも割り当てるのは避けたいという環境で、G1GCどないやろって試した記録。実用レベルにはなったのでしばらく動かしてみるけど、そこまでメリットは…という感じ。

-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCCause を付けて起動しては GCViewer に食わせて眺めるの繰り返しをしていた。

  • 起動完了時に FullGC を手動で実行しているっぽいが、今の構成ではそこまでメリットがないので1secをケチって -XX:+DisableExplicitGC をつける -> いい感じ
  • Young:Tenuredをガッと1G:2Gにしてみる -> GC回数やトータルの時間こそ減っても、最大の停止時間が延びてしまった。恐らくレンダリング周りを中心に短命なオブジェクトが多いので、まとめて掃除するよりは頻繁に片づけた方が停止時間は少なくてすむ。それはそう。

-Xmx2G -Xms2G -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:-UseAdaptiveSizePolicy -XX:+DisableExplicitGC -Xmn128m

で、G1GC。

  • XX:MaxGCPauseMillisを守ろうと頑張ってくれるのだけど、調子にのってYoungがヒープの半分ほどを持っていってしまうことがあり、移動先がなくてFullGCが走ってしまった。
  • -XX:InitiatingHeapOccupancyPercent=30 -XX:G1ReservePercent=30 で積極的にmixedを走らせ余裕も持たせると流石に失敗することはない感じ。でも時々カクつく。やっぱりYoungを絞ることを考えた方がよいのでは。
  • -XX:G1MaxNewSizePercent で十分に絞ると心なしか停止時間も短かくなる?
    • じゃいっそXmnで固定してもいいのではとなるけど、それならCPU負荷低い分CMSでよくない?という感じが。
  • どうせTenuredはなかなか埋まらないので、InitiatingHeapOccupancyPercent は MaxNewSize 近辺かより小さいぐらいでないと意味なさそう。となった時に Young を絞って young GC に仕事してもらう vs InitiatingHeapOccupancyPercent だけ下げて積極的に mixed GC してもらう、みたいなことを思ったけれど、ワーストケースを抑制するにはやはり Young を絞ったほうがいいかなあという感じ。
  • 基本的に潤沢なメモリとCPUが前提というか、それを有効活用してスループットや停止時間を改善する感じはある(12Gもあればいい感じに動くのだろうな)

-Xmx2G -Xms2G -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:G1MaxNewSizePercent=20 -XX:G1ReservePercent=30 -XX:MaxGCPauseMillis=20 -XX:+DisableExplicitGC

どうせそういう運用になるならと2GBにしてみたけど、主に3とか4とかで試していた。

参考

いろいろ読んだけどよーわからんマン。

GC全般とか

G1GCの話

ThunderbirdのAPIを叩いてアドレス帳にリストをインポートする

単純にアドレス帳のエントリをインポートするのは標準機能でできたが、リストを取りこむ方法が見つからなかった。数が多く手打ちも面倒だったので、なんとかしてインポートしたい。ここでふたつの可能性を見つけた。

  • リスト編集画面で メアド→候補が出るまで待つ→Enter ないし 名前 <メアド>→Enter という操作を再現する
  • JavaScriptAPIを叩く

せっかくなので後者に挑戦した。幸い、MDNにアドレス帳を操作するサンプルを見つけることができた。

Address Book examples - Mozilla | MDN

結論から言えば十分な情報が得られたわけではなく、開発者ツールでひたすらメンバや挙動を探ったりしたわけだが。

調査結果

前述のサンプルにもあるが、大筋はこういうことになる。

  • リストの親となるアドレス帳を取得する(列挙はできても名前等で検索する方法はない気がする)
  • アドレス帳からユーザーを検索する
    • addressLists等を列挙する
    • cardForEmailAddress や getCardFromProperty で検索する
    • Lisp式風のクエリで抽出する(論理演算子 and/or/not は必須*1で、それと (フィールド,比較演算子,値) を組みあわせていく)
  • リストの操作
    • 追加
      1. nsIAbDirectoryのインスタンスを作る
      2. isMailListやdirNameを設定する
      3. addressLists.appendElementでエントリを追加する
      4. アドレス帳に addMailList で登録する
    • 取得
      • A: 親の addressLists を列挙する→nsIAbDirectoryが取れる
      • B: クエリでIsMailListがtrueなエントリを抽出する→nsIAbCardが取れる
      • nsIAbCardからnsIAbDirectoryは card.mailListURI を引数に abManager.GetDirectory() を呼びだすことで取得できる
    • 編集
      1. nsIAbDirectoryを取得する
      2. いじる
      3. リスト自身のプロパティは即時反映、addressList は list.editMailListToDatabase(null) で保存する。 *2
    • 削除
      1. nsIAbDirectory を取得、まあしなくてもよい
      2. abManager.deleteAddressBook()URIを渡す

ところでaddressListsプロパティは GetElementAt() で取得するとそのまま nsIAbCard なり nsIAbDirectory として使えたが、enumerate() を経由すると別途 QueryInterface が必要だった。非ジェネリック版の IEnumerator のような感じだろうか。

具体例

各メンバーは既にアドレス帳に登録されていて、それをメアドを見てリストに登録する。最初はリスト名とメアドの配列をJS側で指定することを考えたが、気がついたらリストのdescriptionにメアドをカンマ区切りで書いておいたものを読むようにしていた。

(function(){
    const iterToJS = function*(iter) { while(iter.hasMoreElements()) yield iter.getNext(); };
    const abManager = Components.classes["@mozilla.org/abmanager;1"]
        .getService(Components.interfaces.nsIAbManager);

    for (const directory of iterToJS(abManager.directories)) {
        for (const rawList of iterToJS(directory.addressLists.enumerate())) {
            const list = rawList.QueryInterface(Components.interfaces.nsIAbDirectory);
            const emails = list.description.split(',');
            if (!emails[0].includes('@')) continue;

            console.log(`building list: ${list.dirName}`)

            for (const email of emails) {
                const card = directory.cardForEmailAddress(email);
                if (card) {
                    list.addressLists.appendElement(card, false);
                } else {
                    console.warn(`${email} is not found`);
                }
            }

            list.description = '';
            list.editMailListToDatabase(null);
        }
    }
})();

で、逆にこれをエクスポートしたい時にも困るんだろうなーと逆パターンも適当に書いてみた。ただこちらに関してはリスト自身の情報も含めてJSONかなにかで出力した方が便利かもしれない。というか100件近く登録したらリストの編集画面を開くのに時間がかかってしまってうーんという感じ。

(function(){
    const iterToJS = function*(iter) { while(iter.hasMoreElements()) yield iter.getNext(); };
    const abManager = Components.classes["@mozilla.org/abmanager;1"]
        .getService(Components.interfaces.nsIAbManager);

    for (const directory of iterToJS(abManager.directories)) {
        for (const rawList of iterToJS(directory.addressLists.enumerate())) {
            const list = rawList.QueryInterface(Components.interfaces.nsIAbDirectory);
            if (list.description !== '') continue;

            list.description =
                [...iterToJS(list.addressLists.enumerate())]
                .map(x => x.QueryInterface(Components.interfaces.nsIAbCard).primaryEmail)
                .join(',');
        }
    }
})();

実行方法としては…メインウィンドウで Ctrl+Shift+I を押して開発者ツールを開いてスクラッチパッドで…。

ところで、Thunderbirdのアドレス帳は abook.mab というファイルに保存されているが、どうも削除した連絡先やリストの情報も残っているように見える。だとすると、descriptionに書くやりかたは余計なゴミを残してしまうのでよくないかもしれない。

*1:このあたりドキュメントがなくて結局ソースを見た mailnews/addrbook/src/nsAbQueryStringToExpression.cpp

*2:サンプルではnsIAbCardを変更するよう書かれているが、そもそもThunderbird本体でもツリーから編集画面を開くとnsIAbCardを使っていない気がする。suite/mailnews/addrbook/abCommon.js