低レベルAPIを叩いてプログラムすることの醍醐味

.NET Frameworkのマネージドコードしか体験していない開発者の方もいらっしゃるかと思いますが、Windows APIやC言語のランタイムライブラリの低レベルAPI(ここでいうレベルとは低いほど決め細やかな操作ができる一方で書かなければならないコード量は増えることを意味します)を駆使してシステム構築している人は海外に限らず日本においてもいます。

皆さんは、クリティカルな処理や高いパフォーマンスを要求される処理を実装する場面に出会ったことがあるでしょうか。

低レベルAPIのよさは、まさにクリティカルでハイパフォーマンスを要求される場面で真価を発揮します。

いまどきはC言語プログラマの人口が減少傾向にあるかもしれませんが、C言語のプログラマであれば、たとえばファイルの入出力に2つのレベルの関数が用意されていることを知っていると思います。ファイルポインタを利用するfopen()でファイルを開くタイプと、ハンドルを利用するopen()。ハンドルとかポインタとかいう用語にピンと来ない方もいらっしゃるかもしれません。

.NET Frameworkの良い点は、低レベルAPIを意識することなく、オブジェクト指向のクラスライブラリで面倒な部分を隠蔽化(カプセル化)している点です。一方で、低レベルAPIを叩いてアプリケーションを構築している経験者であれば、内部で何が行われているかが定かでない部分もあって、マネージドコードをすんなり受け入れられないということもあるかもしれません。.NET Frameworkのスタックは、COMサーバにより実現されているということを認識している人もいればそうでない人もいます。細かいことを突き詰めていけばきりがないのですが、一種のプロセス間通信によりアプリケーションの実行されているリソースが他のアプリケーションに影響を及ぼさないように管理できたり、メモリやスレッドなどのシステムリソースを.NET Frameworkの中で管理していることで、細かな低レベルのAPIを知らなくとも(特にメモリ管理)アプリケーションを開発できるというのがマネージドコードの良い面であります。

しかしながら、いくらマネージドと言えど、開発者が低レベルのレイヤーで何が行われているかをまったく知らなくてよいといえるでしょうか。

推測の域を出ないことを事前に断った上で、マネージドコードをうまく使えている開発者というのは、実は低レベルのAPIの中で何が行われているかを事前に頭の中でエミュレートできる人ではないかと思っています。エミュレートしている中でマネージドコードのリスクを管理できる人とも言えるかもしれません。リスクというと否定的な感じを受ける方がいらっしゃるかもしれませんが、システム開発において、開発者は常にリスク(予期せぬ出来事など仕事の進捗に影響を及ぼす事象)にアンテナを高くしておく必要があるかと思います。

たとえば、ガベージコレクタ(GC)という機構がマネージドコードを処理する言語ランタイムに備わっています。これは.NET FrameworkのCLRだけに限らず、Java VMにおいても同様にGCの機構が用意されています。GCのメリットは、メモリ管理においてポインタの概念から開発者を解放することに尽きると思いますが、果たしてGCにすべてを任せてアプリケーションがまともに動作するでしょうか。今から5年くらい前の話になりますが、私は業務命令により、とある企業のJavaのサーバアプリケーションのパフォーマンスのトラブルシュートを担当したことがあります。そのアプリケーション開発は、日本人が仕様を決め、実装はオフショアということでインドの企業に業務委託されていたものでした。現場に向かい、現象を観察し、パフォーマンスモニタで必要なメトリクスを測定した上で、ソースコードを見なくとも、問題が予想できました。「これは、GCがパンクしている・・・」と。その後、Javaのソースコードの現物を見ながら、即座にループを行っている部分をgrepで抽出しました。いくつかの怪しいループがgrepだけで見つかりました。そして、そのループの内部を見たら・・・。

「あのー、この1万回以上の繰り返しの中で、インスタンス生成が繰り返されて、不要なインスタンスが解放されていないのは何故ですか?」

Javaの処理系で文字列を操作するためにStringというクラスがあります。これは基本データ型ではないので、利用においてインスタンス生成がなされ、GCの管理下にメモリが置かれます。し・か・し、Java VMが起動する際のメモリヒープ量には限界があります。(注:Java VMのスタートアップ時のパラメータにより決定されます)
この限度を超えたメモリが消費され、GCがパニック状態になってしまうとどうなるか想像できますか?これは非常に面白い現象なのですが、低レベルAPIで開発経験のある方なら「そんなの当然だろ!」って思うかもしれません。何が起こるかというと、限られたメモリヒープを死守すべくGCが不要なインスタンス(オブジェクト、つまりメモリ空間)を解放しようとするのですが、GCから見てそれが不要なのかどうか判断ができない一方でアプリケーションは繰り返し処理によってメモリをVMに要求します。メモリリソースを解放しようと努力するGCとメモリリソースを要求するアプリケーションに衝突が発生するわけです。この結果、WindowsでタスクマネージャでJava VMを監視しているとCPU利用率がほぼ100%に達します。これにより、アプリケーションがタスクマネージャ上のプロセスとみなされていても、ユーザエクスペリエンスとしては、フリーズしている状態に見えます。GCの落とし穴がここにあるわけです。この事象はJavaに限った話ではなく、GCを有するオブジェクト指向の処理系すべてに共通するリスクです。

話を戻し、低レベルAPIを叩くことの意義というか、OSの上でアプリケーションを開発する上で必要な知識というのは、低レベルのAPIを含めて、アプリケーション層の下位レイヤーで何が行われているかを知っておくことではないかと、今更ながら思います。

.NET Frameworkが3.0に進化する過程でAPIセットには大きな変化があります。その一方で、Windows Vistaにおける低レベルAPIにおける制約が変わっていることにいち早く気がついている開発者もいます。Webアプリケーション、Webサービス、スマートクライアント・・・、ある程度、簡単に可視化できるものは開発効率重視に走っていくことは悪いことではないと思います。しかし、長時間の稼動でクラッシュしない、高トラフィックになったときにきちんと対処できる・・・、そんなアプリケーションを実装しようと思ったら、間違いなく、アプリケーションが稼動している環境のブロックダイアグラムが描けないと困ると思います。

確かに低レベルAPIを叩くことは容易ではないでしょう。でも開発者と名乗る人々には、一度でもいいから高水準のAPIを使わないで同じことを低水準のAPIで実装するということにチャレンジしてもらいたいと思います。その上で、OSが提供する機能を深く理解してもらい、高水準でよい部分は高レベルのAPI、またはクラスライブラリで、一方で、クリティカルな部分は低水準のAPIまたはそれらの機能をラップしたコンポーネント開発をすることで、安全に運用でき、リスクを管理できるような開発を行ってもらいたいと感じます。

そういう努力を積み重ねていれば、たとえば排他制御(Exclusive Control)というものを難しく考えずに単純なものになぞらえて、システムプログラミングができるようになると思います。なぜ、セマフォ、ミューテクス(Mutex)、クリティカルセクション、などという排他制御のための機構が用意されているのか、何故、リソースを利用する前にロックしなければならない状況があるのか、同時実行制御にどんな問題があるのか、頭の中でエミュレートできるようになると思います。また、プロセスが起動してスタートアッププロセスが起動し、アプリケーションが実行される準備が整うまでに何が行われているか、あるいはアプリケーションが終了する際にどんなことが行われるか、またどんなことを開発者はきちんと処理しなければならないのか、プロセス・スレッド・メモリ、ネットワークやストレージを含めたコンピュータのリソース、CPUの優先度、仮想メモリ機構が何故用意されているのか、何故、サービスとデスクトップとの対話というのが単純なものではないのか、インプロセスではなくアウトプロセスでの通信において、ACLを含めた管理が必要なのか、・・・いろんなことに対して、視野が広がる(嫌でも視野を広げざるを得ない・・・というのが正解かもしれない)と思います。

・・・

昨日、つくばで、ソフトイーサの開発を行った登さんとマイクロソフト側の及川さん(Windows開発統括部)との対談をChannel 9のコンテンツとして撮影しながらぐるぐる考えていたことをブログに残してみました。。。

# 今やほとんどビデオ屋さんとなっている私、大西ですが、これでも元はDeveloperなんですぅ (^^;