プロファイラをWin32に移植


従来、nmoshにはSIGPROFをつかったサンプリングプロファイラが有るが、Win32では動かなかった。Win32のスレッドはシグナルのように非同期に割り込むための良い仕組みがない。
Win32には安いthread suspendがあるのでそれを使って実装する。つまり、

static void*
profiler_interrupt_thread(void* p){
    LARGE_INTEGER duetime;
    HANDLE hTimer = NULL;
    VM* vm = reinterpret_cast<VM *>(p);
    // Create new timer(no named, names must be unique)
    hTimer = CreateWaitableTimer(NULL, FALSE, NULL);

    // duetime = 0;
    duetime.QuadPart = -1LL; // wait 100 ns

    SetWaitableTimer(hTimer, &duetime, 10 /* 10 ms */, NULL, NULL, 0);
    for(;;){
        if(WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0){
            // FAIL
            return NULL;
        }else{
            EnterCriticalSection(&vm->profilerCs_);
            if(vm->profilerTerminate_){
                LeaveCriticalSection(&vm->profilerCs_);
                return NULL;
            }
            if(vm->profilerEnable_){
                SuspendThread(vm->vmThread_);
                vm->collectProfile();
                ResumeThread(vm->vmThread_);
            }
            LeaveCriticalSection(&vm->profilerCs_);
        }
    }
}

のようなスレッドを横で走らせ、ときおり対象スレッドのVMコンテキストを横取りしてプロファイラをkickする。
nmoshのVMはいくつかのステートがTLS上にあるので、それらを触らないように注意する必要がある。具体的にはreader。
Waitable timerの周期タイマを使用しているが、冷静に考えるとoneshotタイマを毎回仕掛ける方が正確だな。。(プロファイルの収集中にもGCが発生する可能性があるので)
Win32(に限らず普通のデスクトップOSでは、)タイマは難しい。

In current versions of VirtualDub, I don't use any of these methods for frame timing in preview mode. Instead, I have my own timer queue thread that uses timeGetTime() coupled with a Sleep() loop, with precision boosted by timeBeginPeriod() and with thread priority raised

一般的な手法はタイムアウトのあるAPIを使って自分のタイマを作ることで、多くの場合はこれで上手く行く。libuvはタイムアウトの期間でソートした赤黒木を使って通常のI/Oオブジェクト待ちのタイムアウトを調節している。
そういうことをしなくてもいいように近代的なカーネルでは様々な方法でタイマを提供している; Linuxのtimerfd、MacOS/BSDのkevent、Win32のtimerqueue等。そしてやっぱりここでもOS間の互換性は無い。共通規格としてはPOSIXタイマが有るが、意外と実装されていない - 例えばNetBSDには5.xくらいまで無かった(IIRC)。
いわゆるcall-graphベースのプロファイラも用意したほうが良いような気がしている。Scheme固有の問題 - 無名関数をどうすんのか等が有るけど。