Meadow遅くて死にたい
いい加減なんとかするか。
300文字くらい日本語を書くと、Meadowが露骨に遅くなって、1000文字超えるともはや編集不可能という状態だった。
しかもなんかカーネル空間で時間食ってるっぽくて謎い。
xperf
xperf素晴らしいのでみんな使うべき。Windows SDKを入れて、Install Windows Performance Toolkitからインストールできる。
まず、Meadowのソースを取ってきてビルド。これでpdbを作る。
んで、管理者権限で、
$ xperf -on latency -stackwalk Profile
とする。
- -on : 何を採取するか。CPU時間とるときは latency でよい
- -stackwalk : バックトレースするサンプルを選ぶ。CPUサンプルの名前はProfileなのでProfileを指定
対象プログラムをしばらく動かす。
$ xperf -stop -d hoge.etl
で止める。-d で出力ファイルを指定する。
あんま理解してないのだが、 -d を付けないと、出力結果がc:\kernel.etlに出るのだが、なんかmergeされない(?)らしくシンボルが見れなくなるので、-d hoge.etl は必須。
_NT_SYMBOL_PATH を pdb が置いてあるところに設定。
$ xperf hoge.etl
とかやって結果を見る。
mw32_glyph_metricが重いっぽいというのが確認できる。
(シンボルサーバが設定してあればgdi32とかも見れると思う)
mw32_glyph_metric
大体↓こんな感じ
static XCharStruct * mw32_glyph_metric (MW32LogicalFont *plf, XChar2b *cp, int font_type) { mw32_windows_font *pwf = (mw32_windows_font*) plf->pphys; ... { HANDLE hold; HDC hdc = GetDC (NULL); if (pwf->pfont == INVALID_HANDLE_VALUE) pwf->pfont = CreateFontIndirect (&pwf->logfont); hold = SelectObject (hdc, pwf->pfont); if (pwf->ttfp) { ABC abc; int ret = GetCharABCWidths (hdc, *cp, *cp, &abc); if (ret) { pwf->cur_cm.width = abc.abcA + abc.abcB + abc.abcC; pwf->cur_cm.rbearing = abc.abcA + abc.abcB; pwf->cur_cm.lbearing = abc.abcA; } else { SelectObject (hdc, hold); ReleaseDC (NULL, hdc); return 0; } } else { ... } pwf->cur_cm.ascent = plf->ascent; pwf->cur_cm.descent = plf->descent; pwf->cur_cm.width += get_device_width (hdc, plf->character_spacing); SelectObject (hdc, hold); ReleaseDC (NULL, hdc); } if (plf->encoding.font_unit_byte == 1) pwf->cmcache[*cp] = pwf->cur_cm; return &pwf->cur_cm; }
ベンチ
どうなってるか見ておくか。
#include <stdio.h> #include <windows.h> static double freq_usec; FILE *out; static double t() { LARGE_INTEGER li; QueryPerformanceCounter(&li); return li.LowPart * freq_usec; } #define START_TIMER(name) const char *timer_name = name; double timer_start = t(), now; #define DUMP_NOW(name) name_list[cur_timer] = name; usecs[cur_timer++]=t(); HFONT font = INVALID_HANDLE_VALUE; const char sjis[] = "愛あい"; #define swap16(a) ((((a)&0xff)<<8) | ((a)>>8)) void func() { HDC dc, top_dc; LOGFONT lf; ABC abc; HGDIOBJ old; unsigned short sjis_val; double usecs[32]; int i; const char *name_list[32]; int cur_timer = 0; START_TIMER("dc"); name_list[0] = "start"; usecs[0] = timer_start; cur_timer = 1; dc = GetDC(NULL); //dc = CreateCompatibleDC(top_dc); DUMP_NOW("getdc"); if (font == INVALID_HANDLE_VALUE) { ZeroMemory(&lf, sizeof(lf)); lf.lfHeight = 12; lf.lfCharSet = SHIFTJIS_CHARSET; font = CreateFontIndirect(&lf); } DUMP_NOW("createfont"); old = SelectObject(dc, font); DUMP_NOW("selectobject"); sjis_val = swap16(*(unsigned short*)(sjis+0)); GetCharABCWidths(dc, sjis_val, sjis_val, &abc); DUMP_NOW("width-sjis1"); ZeroMemory(&abc, sizeof(abc)); sjis_val = swap16(*(unsigned short*)(sjis+2)); GetCharABCWidths(dc, sjis_val, sjis_val, &abc); DUMP_NOW("width-sjis2"); //printf("%d %d %d\n", abc.abcA, abc.abcB, abc.abcC); SelectObject(dc, old); DUMP_NOW("selobj"); ReleaseDC(NULL, dc); //DeleteDC(top_dc); DUMP_NOW("reldc"); for (i=1; i<cur_timer; i++) { fprintf(out, "%s:%20s:%10.4f:%10.4f\n", timer_name, name_list[i], usecs[i] - usecs[i-1], usecs[i] - timer_start); } } int main() { int i; LARGE_INTEGER li; QueryPerformanceFrequency(&li); freq_usec = 1000000.0/li.LowPart; out = fopen("log.txt", "wb"); for (i=0; i<4; i++) { func(); fputs("========================================\n", out); } }
こんな感じで測った。(E350マシン)
======================================== dc: getdc: 6.3999: 6.3999 dc: createfont: 0.0000: 6.3999 dc: selectobject: 0.0000: 6.3999 dc: width-sjis1: 21.1197: 27.5197 dc: width-sjis2: 9.5999: 37.1195 dc: selobj: 0.0000: 37.1195 dc: reldc: 15.9998: 53.1194 ========================================
一文字あたり40usecくらいか。300文字( = 12[msec])くらいで気になり始める、という直感と大体あってると思う。
修正
さてどうするか。ハック的に対応するか、真面目に対応するか…
面倒だからハック的にやろう。
(というか見てたらビットマップフォント使えばなんとかなるように見えるが)
問題は、毎回GetDC-ReleaseDCしてるという点で、これだけで20-30usecぐらい。
フォント作るときに、コンパチDC作ってそこから情報とるようにするか…
→した。
一般的なエディタには及ばないが、まあ、我慢できる範囲にはなった。
気持ちいいな。もっと早くやるべきだった。
次は、c:/Program Files/Microsoft SDKs/Windows/v7.0/Include/ でファイル名補完すると5秒停止する問題かな…気が向いたらやろう。多分stat遅い系だろう。
いや
まだ重い。
SelectObject -> GetCharABCWidths で、初期コストかかるっぽいので、
CreateFontIndirectした瞬間にSelectObjectしてみよう。
→した。
80カラムで、画面半分埋まったぐらいなら大丈夫ぐらいになったが、Atomマシンで80カラム超えたり、画面ビッシリ埋めたりするとまだ遅い。
まあいいか…そのうち考えよう。
あと、なんか一回落ちたような…!再現しないので放置するが、どこかおかしいかも。
→修正した、と、思う。なんか気付いた点があればコメントください…
stat遅い系
雑に修正した。
この修正を適用すると以下の点で問題がある
- (何も確認してないが)多分シンボリックリンク回りが弱くなってる気がする
- completion から呼ばれてるコールバックが file-exists-p だった場合は呼ばない。
(file-exists-p を書きかえて補完をカスタマイズすることができなくなる)
Find file: c:/Program Files/Microsoft SDKs/Windows/v7.0/Include/
でタブを押してから[Complete, but not unique]が出るまでの時間が手元のマシンでbefore 5秒 → after 0.5秒くらい。
というかWindowsのstat遅い伝説は、stat->GetFileAttributesに書き換えてもあんま改善しないんだな…
つまり、POSIXだと、
while (1) { readdir() stat() }
と、書いてあるのを、
while (1) { FindNextFile() GetFileAttributes() }
に書き換えてもあんまり改善しない。
FindNextFileから取れる情報は貴重であり、それを捨ててもいけないし、それ以上を求めてもいけない。
まあ、パス文字列→ファイルへの処理って結構面倒そうだしな。いや頑張れよという気がするが。
fork, exec, stat のCygwin殺しが無くなればもうちょっと頑張ってCygwin使おうという気になるんだが。forkは仕方無いとしても、CreateProcessとstatはもうちょっと改善されてよいんではないだろうか。
フォント遅い系
やっぱり遅いので力技で解決した。
フォントサイズのキャッシュが1byte文字の範囲だったのを2byte範囲まで拡大。
まあ、力技と言っても、フォントあたり6byte x 64K = 384KBで、たった0.3MBなので、Core2以降ならLLCの片隅に乗るくらい。