radium.png
HOME | ARCHIVE | PRODUCTS | ABOUT | RSS

LeakTracer

2003-05-01

テストプログラム用のフレームワークとして CppUnit を利用する場合に,最も問題となるのが,メモリ関連のチェック機能が不足していることではないかと思う。これは CppUnit のオリジナルである JUnit が Java での実装であったことに関連しているかもしれない。ともかく,ここでは C++ を用いているのだから,テストと同時にメモリリークの検出ぐらいは行っておきたいものだ。

ここではひとまず,外部ツールの助けを借りて,メモリ周りのチェックを行うことにしようと思う。


メモリの不正利用を検出するためのツールは,意外と多く存在する。

http://www.home.linuxjournal.com/modules.php?op=modload&name...

UNIX 方面で人気があるのは "GNU Check" や "mpatrol", "ElectricFence" といったツールのようだ。これらのツールは,メモリ保護機能を応用して不正な領域へのアクセスを検出し,デバッグ情報と共に表示するというものだ。また同時に,メモリリークの検出も行えるようになっている。

http://www.gnu.org/software/checker/checker.html

http://www.cbmamiga.demon.co.uk/mpatrol/

http://perens.com/FreeSoftware/

http://cs.ecs.baylor.edu/~donahoo/tools/efence/

この他に,ガベッジコレクタの実装を転用してリークデテクタとする方法もあるようだ。 "Boehm-GC" として有名な "The Boehm-Demers-Weiser conservative garbage collector" は,本来ガベッジコレクタの実装であるものの,「おまけ」としてメモリリークを検出する機能も付けられている。元がガベッジコレクタであるため,参照されなくなったメモリ領域だけを正確に検出することができるようになっている。

http://www.hpl.hp.com/personal/Hans_Boehm/gc/


……と,このように,ツールは色々と存在するものの,ここでは「手軽にリークの検出ができること」と,「できるだけシンプルな実装であること」の2つに条件を絞って選択してみようと思う。

そこで最も良さそうだったのが, Erwin Andreasen 氏の "LeakTracer" だ。

http://www.andreasen.org/LeakTracer/

ん,何だか知らないけどサイトが落ちてしまっているな……。この "LeakTracer" は Debian の標準パッケージに含まれているので,そちらからソースを拾ってくることも可能だ。

http://ftp.debian.org/debian/pool/main/l/leaktracer/leaktrac...

パッケージには以下のようなサンプルプログラムが含まれている。

// Small leaky test program

void foo() {
    int *x = new int;
}

int main() {
    int *z = new int[10];
    foo();
    foo();
    delete z;
    delete z;   // delete value twice
}

もう,見るからに不正なプログラムだ……。これを LeakTracer でチェックするには,まず, "LeakCheck" コマンドと共にプログラムを走らせる。これによって,リークポイントの検出結果が "leak.out" ファイルに出力される。

$ LeakCheck ./test

ただし, cygwin 上でこれを行うと,共有ライブラリの結合でコケてしまうようだ。これについては, LeakCheck コマンドを用いる代わりに,オブジェクトファイル (LeakTracer.o) を静的にリンクすることによって対処が可能だ。

$ gcc -g -o test.exe test.cc LeakTracer.o
$ ./test.exe

こうして得られた leak.out ファイルには,リークポイントのアドレス情報が羅列されている。デバッグ情報付きでビルドされたプログラム(いわゆる「"-g" オプション付き」)ならば, "leak-analyze" コマンドを用いることで,ソースとの対応を取ることができる。

$ leak-analyze test.exe
Gathered 3 (2 unique) points of data.
(gdb)
#-- Alloc: Different allocation schemes
alloc here :0x4010d2 is in main (test.cc:8).
7       int main() {
8           int *z = new int[10];
..free here :0x4010ea is in main (test.cc:12).
12          delete z;   // delete value twice

#-- Leak: counted 2x / total Size: 8
0x4010a2 is in foo() (test.cc:4).
3       void foo() {
4           int *x = new int;

#-- delete on not allocated memory: counted 1x
0x4010f5 is in main (test.cc:13).
12          delete z;   // delete value twice
13      }

結構見やすい表示だ。他のツールと比較した場合,機能的には限定されているのだけれど,それがかえって使いやすさに繋がっているような気がする。


LeakTracer の行っていることは,基本的に "operator new" と "operator delete" のオーバーライドだけだ。そこから「delete 忘れ」等の検出を行い,得られた情報を leak.out へ出力する。 "leak-analyze" コマンドの実体は perl スクリプトであり,内部から gdb を呼び出すことによって,アドレス情報とソース内容の対応を可能としている。

このように,内部的にはアドホックな実装なのだけれど,やっていることがシンプルなだけに,ゲーム機環境への組み込みも楽に実現できそうだ。これについては,いずれ機会を見て検討してみようと思っている。


Ruminations on C++

2003-05-02

今日は久しぶりの有休日。せっかくの好天を満喫しようと思い,玉川河川敷へと散歩に出かけた。

休日は常に人で溢れ返っている河川敷も,まだ今日は人もまばらだった。川端のコンクリブロックに腰を据えて,しばらくの間,何をするでもなく,ただ本を読んでいた。

しばらくして日が傾いてくると,急に風が冷たくなってきた。日光浴なら,もっと昼間に来なきゃだめだな……。

5時ぐらいで早々に退散。ファミレスのラザニアとコーヒー2杯で,家に戻ってきた。


散歩のついでに読んでいた本がある。 "Ruminations on C++" の和訳版,「C++再考」だ。

http://www.pearsoned.co.jp/washo/prog/wa_pro62-j.html

http://www.amazon.co.jp/exec/obidos/ASIN/489471776X

先日,いつもの本屋へ立ち寄った際に,何気なく手に取った本なのだけれど,一目見て良い内容だと感じたので,その場で購入を決めたものだ。

この本は,著者の Andrew Koenig 氏が JOOP (Journal of Object-Oriented Programming) 等に連載していたコラムを集め,一冊の本として再編集したものであるらしい。そのためか,一つ一つの章の内容が比較的独立しており,どこからでも読み始めることができるような内容となっている。

ただ一つ気を付けなければならないのが,この本は C++ 入門者に向けた内容ではないということだ。むしろ, C++ や STL を一通り修めた中級者へ向けた内容となっている。文字通り「再考」するための本だということだ。

この本の中で,個人的に最も興味深かったのが第三部の内容だ。

第三部「テンプレート」では, C++ のテンプレートの各種応用法について解説を行いつつ, STL のコアとなる概念「ジェネリシティ」の導入を行う内容となっている。どのようにして「ジェネリックなイテレータ」という概念が導き出されたのか? 関数オブジェクトの本来の目的とは? 関数アダプタは我々に何をもたらすのか? 等々……。 STL の考案者が辿ったであろう思考の道筋を,読者自身の視点から辿り直すことで,その存在意義と応用法についてより正確なイメージを掴むことが可能となっている。

これまでにも,オブジェクト指向に関して解説を行った書籍は巷に山ほどあれど,ジェネリックプログラミングを扱った書籍の数は極めて限られているように思える。一般的な STL の解説書は,ライブラリ自体の解説に終始してしまっているきらいがあるし, "Modern C++ Design" のような凝った本は,内容が専門的過ぎるために導入書としては適さないのではないかと思う。

この本のコンセプトは実に曖昧なものだ。前述の通り C++ の入門書では無いし,いわゆる「ノウハウ本」とも違う。特定の技術について解説した本でも無ければ,実のところ,一定のテーマすらも与えられていないような雰囲気がある。しかし,この本に詰め込まれた数々の考察は,いわゆる「C++ 中級プログラマ」に対して幅広い俯瞰を与え,これまでバラバラに学んできたことを体系的な概念としてまとめる手助けとなってくれるのではないかと思う。


同じ著者によって書かれた本に "Accelerated C++" というものがある。

http://www.pearsoned.co.jp/washo/prog/wa_pro51-j.html

ううむ……目次だけじゃ判らないけれど,興味の惹かれる内容ではある。機会があれば読んでみようと思う。


秋葉原行

2003-05-03

唐突に秋葉原へ出かけてみた。

学生の頃は毎週のように通っていたものだけれど(あの街全体が学校の購買部のようなものだった),最近では行く機会が極端に少なくなってしまった。年に1回,行くか行かないか,ぐらいのものだと思う。

今日も特に目的があったわけでも無くて,ただなんとなく,どの程度雰囲気が変わったものかなと,確認をしに行ったようなものだった。

様々な伝聞から,もう相当にヤバい萌え都市と化してしまった様子を想像していたのだけれど,久しぶりの秋葉原は,案外に大した変化も無くて,少し拍子抜けするようなものだった。それでも,相当に特殊な都市であることには違い無いのだけれど……。

目的も無く,末広町方面の裏路地を,ただぶらぶらと歩いていると,頻繁にジャンクショップの類を目にすることに気が付いた。お馴染みの「ガレージショップ」を始め,「じゃんぱら」やら「掘出屋権兵衛」やら,結構な数のジャンクショップを目にする。休日ということで露天も多かったし,何故か国籍不明の人達の姿もよく見かける。

そういったジャンクショップで扱っている商品と言えば,中古 PC や中古ワークステーション(Sun や DEC や SGI のマシンもゴロゴロ),放送用機材や謎の計測機具,型番落ちのマイクロソフト製品を始めとするゴミのようなソフトの山,等々……。他なら産業廃棄物も同然であろう代物に,堂々と値札を貼って売りさばいている。客の方も,物珍しさに惹かれて来ている人が多いようだ。あまり売れているような気配は感じられない。

放送機材のテレビモニタが 5,000 円だった。あれは確か PAL も映るヤツだから,何かの役には立つかもな……。買わないけど。


適当なレコード屋に入って,キューブリックの「フルメタルジャケット」の DVD を購入。ここまで来て何も買わないのは少し寂しかったので,紛らわしに買ってみたまでだ。

それと,某ゲーム店で PS 版「プリルラ」を発見したので,即購入。これは思わぬ収穫だった。

http://happygamers.fc2web.com/review/prurira.htm

これを証拠物件として提出し,某氏を尋問するのだ。うひひ……。


今回,秋葉原まで遠征したもう一つの理由は, GBA 用の特殊な機材を調達することにあったのだけれど,これは実現することができなかった。どこも店頭では取り扱っていないからだ。まあ,使い方によっちゃあ,非常によろしくないことも出来てしまうツールだから,無理もないのだけれど……。

http://optimize.ath.cx/bootcable/

よく知られているように, GBA のソフトには,1つのカートリッジで複数人の対戦プレイに対応しているものがある。この場合,子機におけるプログラムやデータの類は,対戦ケーブルを介して親機側からダウンロードされ,本体内蔵の 256kB RAM に格納されることになる。これと同様のことを,親機を PC として行えば,任意のプログラムをダウンロードして起動することが可能となるのではないか……というのが「ブートケーブル」の原理だ。

今では,この「ブートケーブル」を利用して, GBA 本体を SRAM 書き込みデバイスとして利用する方法が一般的となっているようだ。 "Flash2advance" 等の市販製品も,基本的には同様のテクニックを利用したものであるらしい。

http://www.flash2advance.com/

しかも驚いたことに,この仕組みを小型ドングルによって実現するテクニックまで開発されているようだ。

http://optimize.ath.cx/bootcable/bootchip.html

これを使えば,例えば上のページでも挙げられているように,ゲームセーブ用の SRAM に任意のプログラムとデータを格納し,それをドングルから起動する,というようなテクニックが実現可能となる。もはや何をしたいのか分からなくなってきている感はあるけれど……相当に楽しそうなハックであることは伝わってくる。


さわやか漂流記

2003-05-04

普通の休日。散歩中に立ち寄った本屋で,クーロン黒沢氏の新刊「マイコン少年さわやか漂流記」を購入した。

http://bookweb.kinokuniya.co.jp/guest/cgi-bin/wshosea.cgi?W-...

アンダーグラウンドの伝道師として有名な氏の,暗澹たる少年時代の記憶を綴った本だ。氏のサイトのプロフィールでは数行にまとめられてしまっていることを,じっくりと一冊の本をもって語り尽くす……というような内容になっている。

http://www.hehehe.net/users/kowloon/profile/

国産PCの創世記に少年時代を迎えた氏の生い立ちは,他のPCフリーク(例えばスパタ斎藤氏など)と重ねて見れる部分も多い。しかし,そこでほんの少し違った過程を歩んでしまったがために,最終的にはまったく異なった種類の人生を歩む結果となってしまった。その,氏の方向性を決定付ける要因となった青春時代の出来事の数々が,実に生々しいドキュメントとして描かれている。

本の中で描かれる氏の少年時代の像は,決してフリークや地下世界の住人としての存在では無く,ごく普通の気弱な少年でしかない。ただそれが,周囲の特異な環境や,持ち前の無謀さ,個性的な知人の存在などによって,徐々に薄暗い世界へと吸い込まれていくことになる。

氏が後書きで述べている「十年前の日本にはチャンスがそこらに転がっていた」という言葉が不思議と胸に残る。80年代後半から90年代初期にかけてのPC創世記は,何にでも,何処にでも,あらゆる可能性が存在していた。カンブリア紀の生物が進化の多様性に挑戦したように,国内のPC産業とそれを取り巻く環境は,己の存在を試すべく,あらゆる可能性へと挑戦をかけていった。氏が体験したような暗部の存在も,その時代に生まれるべくして生まれ,消えるべくして消えた流れの一端なのだろうと思う。

氏は PC88 の同人ソフト界でも特異な活動を行っていたということで,その辺りの話にはかなり親近感を覚える。

http://www.google.co.jp/search?q=%83J%83%7D%83_%83%7E%83A%83...

自主制作の暗部についても生々しい体験談が綴られている。どのような事情によって,あのような謎のラインナップが生み出されることになったのか,その真相が初めてここで明かされることになる。青臭いだけならまだしも,読んでてここまで気まずい思いをするのも珍しいな……。


今となっては過ぎ去った日々の出来事なのだけれど,その裏舞台を知ることができるのは楽しいことだ。あの時,自分が田舎でのうのうと暮らしていた時に,確かにその世界が存在したという確証を得たわけだ。……きっと何処かに存在すると信じていた,刺激溢るる紫色の桃源郷を……

やはり,生まれた環境の影響は重要な問題であると認識せざるを得ない。氏の体験も,周囲の環境や知人による影響が強く表れているようだし,少年期にどのような体験をしたかでその後の人生が決まってしまうというのも,あながち適当な話でもないのだろうなと強く感じた。


Optimization Strategies

2003-05-05

突然だけど, Pete Isensee 氏のページが面白い。

http://www.tantalon.com/pete.htm

氏は数週間前に Gamasutra で "Developing Online Console Games" という記事を書いていた人物だ。マイクロソフト内部で Xbox のソフトウェアエンジニアという役割を担いつつも,わりと中立的な視点で記事を書いている所に興味を惹かれた。氏の個人サイトの方には,氏が過去の GDC で行った講演の資料などが置かれている。特に, Xbox ローンチ時の内情を明かした講演 "Xbox Launch: Lessons Learned" などは,プログラマ以外の人でも興味を持てるものなのではないかと思う。

http://www.tantalon.com/pete/xboxlessonslearned_files/frame....

氏は,以前から C++ の応用に関して強く興味を持っているようだ。過去の Game Programming Gems にも C++ 関連の記事を数個寄稿しているほか, GDC でも C++ 関連の話題を中心に講演を行っている。例えば, 1998 年の GDC 講演 "C++ Optimization Strategies and Techniques" では,氏がゲーム開発のために組み上げた C++ 設計方針とその効果について詳しい解説を行っている。

http://www.tantalon.com/pete/cppopt/main.htm

ただし,若干冗長で羅列的な内容となっているため,既にこの辺りのノウハウを蓄積している人にとっては,あまり得るところの無い内容となっているかもしれない。それでも,実測での速度比較を眺めるのは面白いと思うし(意外と効果の小さな項目が多いことに注目したい),中には新たな発見があるかもしれないと思う。


例えば,仮想関数を持つクラス(仮想クラス)においては,コンストラクション/デストラクションに対して追加の負荷が要求されるという話は,ここで初めて聞いたような気がする。

http://www.tantalon.com/pete/cppopt/final.htm#AvoidVirtualFu...

これは,コンストラクタが継承ツリーを順に辿っていく段階において,タイプ情報を段階的に変更しなければならない,という仕様に起因するものだ。

例えば,次のようなコードを用意する。

struct BaseClass
{
    virtual void display() const = 0;
};

void test_func(const BaseClass* o)
{
    o->display();
    std::cout << typeid(*o).name() << std::endl;
}

struct ClassA : public BaseClass
{
    void display() const
    {
        std::cout << "A,";
    }

    ClassA()
    {
        test_func(this);
    }
};

struct ClassB : public ClassA
{
    void display() const
    {
        std::cout << "B,";
    }

    ClassB()
    {
        test_func(this);
    }
};

ClassA は BaseClass の派生であり, ClassB は ClassA の派生だ。それぞれのコンストラクタでは外部関数 test_func を呼んでおり,その中では仮想メンバ関数 display の呼び出しと,タイプ情報の取得を行っている。

ここで, ClassB の生成を行うと,次のような出力が得られる。

A,6ClassA
B,6ClassB

この出力から分かるのは,例え ClassB の生成であっても, ClassA のコンストラクタの時点では,インスタンスには ClassA のタイプ情報と仮想関数テーブル (vtable) が設定されているということだ。そして,これらのタイプ情報と vtable は, ClassB のコンストラクタが呼び出される直前に, ClassB のものへと入れ換えが行われる。これらの処理はコンパイラによって自動的に生成されるものだ。もちろん,継承ツリーが長くなればなるほど,この処理も長く複雑なものへと成長することになる。


この辺りの処理の詳細については, C++ の規格を当たるよりも,各処理系の ABI (Application Binary Interface) を参照した方が良い場合もある。 ABI は,各言語処理系におけるバイナリ化の規約を集めたものであり,例えば,関数 Foo::bar() のシンボル名はどう変換されるだとか,引数の ** 番目までが ** レジスタに割り当てられるだとか,そういったものが延々と定義されている。 C++ の場合は,タイプ情報の格納方法や,仮想関数の実装方法なども,すべてここに含まれることになる。

どうやら GCC では 3.0 系より CodeSourcery 社の定義する "Itanium C++ ABI" を採用しているようなので,これを参照することにする。

http://www.codesourcery.com/cxx-abi

ドラフトの 2.6 章 "Virtual tables During Object Construction" が今回の件に相当する個所のようだ。

http://www.codesourcery.com/cxx-abi/abi.html#vtable-ctor

しかし,いかにも仕様書って感じの記述で読みづらい。ていうか,読んでも全然分からないや……。ええと,とりあえず,仮想クラスのコンストラクション処理が,予想以上に小難しいってのは,なんとなく分かったよ……。


Embracing STL

2003-05-06

Pete Isensee 氏は,ゲーム制作現場における C++ の啓蒙活動を行うと共に, STL に関しても積極的な啓発を行っている。 GDC 1999 の講演 "Embracing the C++ STL" は,ゲーム開発において STL を活用する際に,特に重要となるであろう項目について触れたものだ。

http://www.tantalon.com/pete/roadtrip99_files/frame.htm

http://www.tantalon.com/pete/files/embracingstl.zip

内容的には,やや基礎的な部類に入るものであり,これから STL を導入しようという人達へ向けたものであることが感じられる。僕がこれを読んで初めて知ったのは, std::deque が "deck" と発音するらしいということだ。ずっと「ディキュー」だと思ってたよ……。

もう一つの資料 "STL Optimization Techniques" は, GDC 2001 で開かれた同名のラウンドテーブル・ディスカッションの内容をまとめたものだ。

http://www.tantalon.com/pete/gdc2001roundtablereport.htm

ディスカッションに参加した約 80 名のデベロッパが,自身のグループにおいて採用しているポリシーや,テクニック,体験談などについて色々と語っている。こちらの資料は,実際の制作現場において STL がどのような扱いをされているのか,という点を知るために参考になるのではないかと思う。なかなか,他所様の雰囲気を知る機会ってのは少ないものだからね……。

ただ,これは GDC 2001 におけるディスカッションとのことだから,多少の内容の古さは考慮しておいた方が良いかもしれない。 2001 年と言うと,ちょうど現世代機への移行が完了した頃であり, C から C++ への移行も過渡期であっただろうと考えられるからだ。現世代機での反復期に入った今となっては,また新たなノウハウを蓄積し始めている所も多いのではないかと思う。

レポートに軽く目を通してみた感じでは,やはり限定的な利用に留めている所が多いな,という印象がある。「コードの増加を防ぐために void* のコンテナを利用している」なんていう意見は,ひどく野暮ったく感じる人もいるだろうと思う。果たして,そこまでストイックにならなければ駄目なのだろうか?

これは C++ の話になるのだけれど, RTTI や仮想関数,例外処理などについても,全面的に禁止するというポリシーが常識的に適用されているようだ。しかし,これらの問題については,もう少し慎重に吟味してみたいと考えている。「結局同じようなものを自前で実装するケース」が何処かで発生するのは必然であり,それならば何のために利用を禁止するのだろうかという疑問が常に付きまとうからだ。盲目的に禁じるのではなく,具体的に「駄目な理由」を挙げることが出来ればと考えている。


少し話は逸れるのだけれど,件のページから, STL の作者である Alex Stepanov 氏のインタビュー記事へのリンクが張られている。ポータブルな STL 実装として知られる "STLport" のサイト内にある記事のようだ。

http://www.stlport.org/resources/StepanovUSA.html

氏は旧ソ連生まれの計算機科学の専門家であり,ソビエトではパワープラントのシステム設計等を行いつつ,渡米以降は主に研究者として様々な舞台で活躍しているようだ。このインタビューの中で,氏は, STL と「ジェネリックプログラミング」の発祥の背景について様々な逸話を披露している。

その中でも特に興味深かったのが, STL の起源について触れている個所だ。

1976 年のソ連での出来事だ。
私は生魚を食してしまったことから,ひどい食中毒に見舞われていた。

病院のベッドで朦朧としていると,私は突然,加算処理を並列化する技術の存在が,
加算が結合的であるという事実に依存していることを悟るに至った。
(簡単に言えば,STL は私の食中毒体験の産物であるということだ)

これを別の言葉で言い換えれば,並列還元を行うためのアルゴリズムは,
代数学で言うところの半群型と結び付けることができるということになる。

このことは,根源に関わるポイントである。
つまり,アルゴリズムは代数構造によって定義することが可能であるということなのだ。

氏は,自身のことを「数学者になりきれなかった人間」というように評している。しかし, STL の優れた設計方針は,氏の持つ技術者としての経験と,数学者としての直感が組み合わさることによって生まれた,まさに必然の産物なのだろうと思う。


Guru of the Week

2003-05-07

Pete Isensee 氏のページから張られているリンクの中に,もう一つ興味を惹かれるものがあった。 "Guru of the Week" − 通称 "GotW" のページだ。

http://www.gotw.ca/gotw/

"Guru of the Week" は, C++ コラムニスト兼コンサルタントとして有名な Herb Sutter 氏のサイト内にある人気コンテンツだ。氏がニュースグループ (comp.lang.c++.moderated) に投稿していたクイズ形式のコラムをコンテンツとしてまとめたものであるらしい。

この "GotW" は,書籍としても出版されている。 "Exceptinal C++" だ。

http://www.gotw.ca/publications/xc++.htm

http://www.pearsoned.co.jp/washo/prog/wa_pro34-j.html

GotW 内の Q&A のうち,最初の 30 問を "Exceptional C++" に収録し,続く 30 問を "More Exceptional C++" に収録しているとのことだ。 "Exceptional" の方は和訳版が出されているのだけれど, "More Exceptional" の方は未訳であるらしい。

"Exceptional C++" の存在は,それとなく知っていたのだけれど,この手のノウハウ本は基礎を固めてからでないと楽しめないだろうと思い,今まで購入を控えていた。しかし……そろそろ手を出してみてもいいかなあ。

http://www.amazon.co.jp/exec/obidos/ASIN/4894712709

結局のところ, C++ という言語は十分に複雑な世界であるため,知らないと損をする罠がそこいらに転がっている。そういった罠を避けるためのノウハウを蓄積することが,駆け出しの時期には重要なタスクとなるのだろうと思う。もちろん,そういう下手な工夫を必要とされない言語こそが理想であるのだろうけども,どんな言語であれ少なからず覚えなくてはならないことが存在するのだから,むしろ優れた資料が豊富に存在することを利点と考えるのが良いのではないかと思う。


Isensee 氏のページでも挙げられているものなのだけれど, GotW #54 の "Using Vector and Deque" などは,ゲーム開発にも深く関わってくるところかもしれない。

http://www.gotw.ca/gotw/054.htm

ここで挙げられているのは, std::vector のメモリ開放手順についてだ。まず前提として, vector は基本的にメモリの開放を行わないものと仮定することができる。これは,メモリの再確保(再配置)の発生頻度を出来るだけ減らすためだ。例として SGI の実装を参照してみると,「バッファのオーバーフローが発生する度に容量を2倍にする」というような記述は見られるものの,開放についてはまったく触れられていないことが分かる。

http://www.sgi.com/tech/stl/Vector.html

reserve はメモリを拡張する方向にしか働かないし, clear を発行したとしても,ほとんどの実装ではメモリを開放しないようだ。

ここで GotW は,メモリの完全な開放を行うには,以下のような「慣用句」を用いれば良いと指摘している。

vector<T>().swap(c);

同様に,メモリ容量 (capacity) を現在の実サイズ (size) まで減退させるには,以下の慣用句を用いる。

vector<T>(c).swap(c);

ううむ,ちょっとトリッキーな表記だ。こういうのはあまり好きじゃないんだけどな……。ともかく,覚えておいて損は無いだろうと思う。


もう一つ挙げられているのが,「vector よりも deque を使え」という指摘だ。

ごく一般的な C++/STL の教科書では, vector を標準的なコンテナとして扱っている。これは C++ 規格の中でもそのような定義がなされているようだ。しかし, GotW の指摘によれば,メモリ効率の良さや挿入処理の早さ,オペレーションの多彩さなどを考慮した場合, deque の方が総合的に優れた結果を導き出すことができるとしている。つまり, vector よりも deque を標準的なコンテナとして用いよう,ということだ。

実際には,挿入・追加処理を除けば vector の方が高速であるため,「何にでも deque を使えば良い」という具合にはならないだろうと思う。ただ, list の代わりに deque を利用した方が良いケースが存在することは確かだ。

ここでちょっと面白かったのが,以下の記述だ。

... a deque allocates its storage in pages, or "chunks," with a fixed number
of contained elements in each page; this why a deque is often compared to
(and pronounced as) a "deck" of cards, ...

へへ,なるほど。それで「デック」ってわけか……。


風邪

2003-05-08

なんだか知らないけれど,朝から喉が痛い。風邪をひいてしまったみたいだ。

明日は健康診断なんだけどなあ……,不健康な状態で健康診断とか受けて大丈夫なんだろうか。

余計なものが出ると困るので,風邪薬は飲まないことにする。


そんなわけで,いつもより早めに帰宅して,とっとと寝ることにした。


Custom Allocator

2003-05-09

朝になっても体調は悪いままだった。身体が重い。通勤で電車に乗ることを思うと気が滅入る。何も無かったら休んでいたと思うのだけれど,健康診断があるので出社することにした。再検査とかなると,また面倒なことになるし……。

健康診断を終えて風邪薬を飲むと,だいぶ気が楽になってきた。相変わらず頭はクラクラするけれど,もう何となく治りかけている感じだ。


GDC 2003 における Pete Isensee 氏の講演は "Custom STL Allocators" という題目だった。用途に合わせてアロケータを使い分けよう,という内容のものであり, Game Programming Gems 3 にも同名の記事を寄稿している。

http://www.tantalon.com/pete/customallocators_files/frame.ht...

http://www.tantalon.com/pete/files/customallocators.zip

この講演の中で氏は, STL のアロケータの仕様について簡単な解説を行っている。自前で実装を行うためのノウハウについても述べており,メモリを厳密に管理しようと考えている人にとっては,参考になるものがあるだろうと思う。

GPG 3 の記事では,実際に数個のアロケータを実装し,その内容を公開している。 Win32API の HeapCreate / HeapAlloc 関数を用いてメモリ確保を行う "HeapAlloc" や,スタック上に確保した配列をアロケータ化する "StackAlloc" などがその一例だ。特に StackAlloc は利用価値が高いのではないかと思う。テンポラリとして STL コンテナを利用する場合などに効果を上げられそうだ。

http://www.tantalon.com/pete/files/gems3update.zip

また,これと同時に,STL アロケータ用のテストスイートも公開されている。自作したアロケータの動作を検証する際に,これを役立てることができるかもしれない。

http://www.tantalon.com/pete/files/stlallocatortestbed.zip


このように, Isensee 氏は自作アロケータの必要性について説いているのだけれど,実際には,アロケータを自作しなければならないシチュエーションは非常に限られているように思える。大抵の場合,デフォルトアロケータ (std::allocator) が適度に最適化された実装を提供してくれるからだ。

例えば, GCC の C++ 標準ライブラリ (libstdc++) におけるデフォルトアロケータは,内部でメモリのプーリングを行うようになっている。メモリの確保は適度な量をもって一括に行われ,開放要求が来ても保持を続けることで再確保のオーバーヘッドを削減している。また,この内部プールは異なるコンテナ間でも共有されるため,非常に効率良く再利用が行われるようになっている。実際に動作を観察してみると,システムコール (new/delete) の呼ばれる回数は極力少なくなっていることが分かる。この処理は,ほとんどのケースにおいて適切な効果を上げているようだ。

http://gcc.gnu.org/onlinedocs/libstdc++/ext/howto.html#3

しかし,逆に言えば,メモリをタイトに切り詰めたい場合には,自前のアロケータが必要となるかもしれない。デフォルトアロケータでは,実際には利用されていない領域まで余計に保持しているかもしれないからだ。また,この手のプーリング処理はメモリの残存量を見え難くする性質があるため,組み込み環境などのようにメモリ容量が制限された状況においては,あまり好ましくない挙動を見せる可能性がある。


メモリをタイトに管理したい場合に役立つのが, Doug Lea 氏の malloc ルーチンだ。

http://gee.cs.oswego.edu/dl/html/malloc.html

この malloc ルーチン(通称 "dlmalloc")は,ごく一般的な用途を想定して最適化されたものだ。これと言って特徴のあるものでも無いのだけれど,非常に素直な挙動を見せるルーチンであるため,クセのあるシステムコールを囲い込んでしまいたい場合などに役立てることができる。コンシューマゲーム機においては,メモリの管理が非常にクリティカルな問題となるため,このような「囲い込み」は意味のある行為となる。

前述のようなデフォルトアロケータの挙動も,問題になるようであれば dlmalloc で囲い込んでしまった方が良いかもしれない。ただ,速度面に多少不安な要素が残るため,後に比較検証を行いつつ方針を決定したいと考えている。


Boost Pool Library

2003-05-10

メモリ管理と言えば,最近になって目を付け始めているのが, boost の Pool ライブラリだ。

http://boost.org/libs/pool/doc/index.html

この Pool ライブラリは,まとまった容量のメモリ領域を一括に確保し,それを固定長のブロックに切り分けて再分配を行うという機能を提供するものだ。細かなメモリブロックの一元管理を可能にするというメリットのほか,再分配処理が非常に高速であるという特徴がある。この辺りの仕組みについては,同ライブラリのドキュメント内の "Pool Concepts" の項に詳しく解説されている。

http://boost.org/libs/pool/doc/concepts.html

唯一の制限は,再分配の際のサイズが固定されてしまっているということだ。例えば requested_size = 128 で初期化した Pool は, 128 バイト単位でしか malloc を行うことができない。

従って, Pool ライブラリの利用は,ある決まったサイズのオブジェクトを大量に生成するようなシチュエーションに限られるだろうと思う。しかし,その特性と用途が正しく合致していれば,非常に優れたソリューションを提供してくれるはずだ。


Pool ライブラリには多種のインタフェースが用意されている。 STL 用のアロケータである "pool_allocator" などは,その一例だ。

http://boost.org/libs/pool/doc/interfaces/pool_alloc.html

この "pool_allocator" を利用すれば,任意の STL コンテナに対して Pool ライブラリのメカニズムを適用することができる。常に固定されたサイズの領域を確保する list や deque とは相性が良いだろうと思う。逆に,コンテナが増える度に領域の拡張を行う vector などでは,まったく効果を上げられないどころか,非常に効率の悪い挙動となってしまう恐れがある。

pool_allocator は,テンプレート引数に指定された型のサイズを元に singleton_pool を導出し,そこからメモリの確保を行うという仕様になっている。しかし,この仕組みには少し裏があって,例えば,

typedef boost::pool_allocator<int> IntAtor

IntAtor my_ator;

std::list<int, IntAtor> my_list(my_ator);

このようなコードを組んでも,実際には RequestedSize = sizeof(int) の singleton_pool が生成されることは無い。上のコードでは型として int を指定した "my_alloc" を std::list のコンストラクタへ渡しているものの,これはコンストラクタ内部で別の形式へと rebind が行われ,実際には異なったサイズの singleton_pool が生成されることになる(この場合,恐らく RequestedSize = 12 となるだろう)。

これは, STL コンテナのサイズを正しく特定するために必要な手続きではあるものの,場合によっては,勝手に任意のサイズの singleton_pool を作成されては困る場合もあるかもしれない。少し注意が必要だ。


"Pool Concepts" のページには以下のような記述が見られる。

Of course, this is a worst-case scenario. However, more modern programs are
making use of small objects on the heap; and that is making this problem more
and more apparent. Wilson et. al. state that an average-case memory overhead
is about ten to twenty percent.

いかにストイックなプログラマでも, C++ / STL を本格的に使い始めれば,細かなメモリブロックの乱造は避け難いものとなってしまう。また,ゲームプログラミングの場合は,これと同時に,慢性的なメモリ不足とも戦い続けなければならないという宿命がある。従って,それぞれの道具の特性を正しく把握し,要所においてそれらを使い分けることが重要な作業となるのだろうと思う。


UNIX USER

2003-05-11

散歩中に立ち寄った駅前の本屋で, UNIX USER 誌を購入した。

http://www.unixuser.jp/magazine/2003/200306.html

なんとなく面白そうな記事が多かったのと,西田亙氏の短期連載企画「Linux から目覚めるぼくらのゲームボーイ」に興味を惹かれたからだ。

http://www.skyfree.org/jpn/unixuser/gba.html

GBA をターゲットとして, GCC を利用したクロス開発を体験してみよう,という趣旨の連載だ。第一回目の記事では, GBA 本体のアーキテクチャと「ブートケーブル」に関する簡単な説明のほか, arm-gba-elf をターゲットとしたクロス環境の構築などについて触れている。

西田氏は他に, UNIX User 誌上で「GCC プログラミング工房」という連載記事を執筆している。

http://www.skyfree.org/jpn/unixuser/index.html

「GCC プログラミング」と題されているものの,実際には Linux kernel や x86 CPU, PC-AT アーキテクチャなど,話題は多岐に渡っている。ソフトウェアの基盤を構成するこれらの要素は,今や大半のソフトウェア技術者にとってはブラックボックス的な存在となってしまっている。それらの要素に対する理解を深め,ソフトウェアの動く「メカニズム」を把握しようというのが,この連載の趣旨であるようだ。 GCC はそのために選ばれた一つのテーマでしかなく,いざとなれば何でも解説してやろうという気概が,この連載からは伝わってくる。現に, GCC 以外について解説しているページの方が圧倒的に多いし……。

普通に PC プラットフォーム上でソフトウェアを開発する分には知らなくて済む知識も,クロスプラットフォームでの組み込み環境向けの開発ともなれば,必須となってしまうことも少なくない。それらの知識を探し求める際に,このような記事の存在は,解答までの道程のショートカットとして大いに役に立つことだろうと思う。

UNIX USER 誌は毎月購入しているわけではないので,単行本化などされると嬉しいのだけれど……。どうだろう。


perl or die

2003-05-12

とある perl モジュールのソースを眺めていて気が付いたことなのだけれど, perl のコードは,時折,妙な具合に文芸的な表現を含んでいるように感じられることがある。例えば, perl には次のような有名な慣用句がある。

open IN, 'foo.txt' or die;

この "or die" 記法は, perl の基礎的な文法として教科書にも登場してくるようなものだ。まだ perl を真面目に勉強している頃ならば,ただありのままにこれを受け入れることができるのだけれど,改めて見返してみると,ひどく間抜けな表記のようにも思える。「ファイルを開け − さもなくば死だ」

"die" コマンドは perl のエラー処理の一種で,引数として渡されたリストを標準エラー出力へ通しながら exit をコールする,というものだ。

http://www.perldoc.com/perl5.6/pod/func/die.html

"or" は単なる論理式の "or" であり,文全体としては,「式の値が決定されると,それ以降の要素は評価されない」という規則を利用したトリックとなっている。つまり, open が false 値を返さない限り die が呼ばれることは無い。

perl は文法に対して非常に大らかなポリシーを持っていることで有名だ。これは,同じ機能を実現するための構文が幾通りも存在することを意味している。しかし,このような「いい加減さ」が perl の嫌われる要因であることは間違い無い。プログラマ毎に異なった「クセ」を付ける隙を与えてしまっていることは,一般に非難されるべきことだと考えられているようだ。

その一方で,構文が多彩であるがゆえに,状況に合わせた自由な文の組み立てが可能であるという利点もある。もっとも,本当の実益主義者にとっては,これは利点とはなり得ないのかもしれないけれど……。

このようなことから, perl という言語は,実務主義的な側面と,文芸的な側面という,プログラミング言語としては特異な要素を兼ね備えた,実にユニークな存在であると言えるのではないかと思う。

http://munitions.vipul.net/documents/rsafin.html


もう一つ,僕の短い perl 歴の中でも印象深かった経験が,後置型の条件文の存在だ。

&append_elem($x) if $x > 0;

英文では if 以降の文節を文の後方へ配置するケースが多いことから,このような構文は「しっくりくる」ものがあるのだろうと思う。実際, if 以降に記述する条件が限定的なものである場合,これを後ろに配置することは見た目の上での利点があるように思える。条件式があくまでも副次的なものであり,関数のコールが主目的であることを強調できるからだ。

これらの構文は C / C++ でも,いくらか真似をすることができる。例えば,関数 foobar の返値が 0 であることを確認する assert 文を書くとしよう。

assert(foobar() == 0);

このコードには大小の問題が含まれている。まず大きな問題は,リリースバージョンにおいて assert を空マクロとして定義すると,関数 foobar が呼ばれなくなってしまうという問題だ。そしてもう一方の小さな問題は,文全体を "assert()" が支配しているために,この文が関数 foobar を呼ぶものであるという意味合いが,若干薄れてしまっているように感じられることだ。

例えば,これを次のように書いてみるのはどうだろうか。

foobar() == 0 || die();

これならば foobar を呼んでいることは明確になるし, die() を "0" と置き換えても問題無く評価されるようになるし……いや,これ以上頑張っても,逆に変態扱いされるだけだからやめておこう……。


E3

2003-05-13

今日は E3 隊の出発日だった。会場は東海岸のロサンゼルスコンベンションセンター。移動に飛行機で9時間も掛かるという話だ。移動するだけでも大変なこったなあ……。

主要メンバーがごっそりと E3 へ行ってしまったので,仕事の勝手がいつもと違う感じになっている。しかし,そんな間にも片付けておくべき宿題がいくつか残されている。順次手を付けていかなければ。

昼過ぎにもなると, E3 関連の情報が徐々に入り始めてきた。昨日,マイクロソフトのカンファレンスが行われたようで, Xbox 関連のニュースが怒涛の如く舞い込んでくる。

その中でも,やはり最大の目玉は "Halo 2" だ。

http://www2u.biglobe.ne.jp/~nanko/e32003/03-halo2.html

相変わらず恰好いいデモをぶつけてくるなあ……。敵味方入り乱れる戦場へと突入するマスターチーフ。圧倒的に不利な状況にも恐れずに立ち向かう姿は,単純な構図だけれど至極恰好いい。彼の寡黙なキャラクタと,顔を常に覆い隠しているヘルメットは,プレイヤーの感情移入を高めるための演出であるらしいのだけれど,その一方で,背後に確立された自己の存在を感じ取ることができる。渋くてストイックな軍人的ヒーロー像だ。

アメリカの制作集団は,徐々に戦争ゲームを作り出すことに小慣れてきているような印象がある。 "Medal of Honor", "Battlefield 1942", "Operation Flashpoint" 等々……,単なるガンシューターの存在を超えて,戦争を一つのテーマとして捉えたゲーム作りが始められている。今回の "Halo 2" のデモは,そんなトレンドの匂いを強く感じる。

そう言えば,カーネギーメロン大学にて行われた Halo 2 プレゼンの内容が "unlimited lives" というサイトで公開されている。

http://www.unlimitedlives.com/special_reports/ul_spec_halo2-...

これは,同大学内にある "MSImpact" と呼ばれるマイクロソフト関連組織(詳細はよく分からない)の主催で行われたもののようだ。

http://www.msimpact.net/

このプレゼンテーションでは, Bungie Studios の Adrian Perez 氏が,初代 Halo における失敗の回顧と, Halo 2 に向けての挑戦の解説を行っている。その, Halo 2 における挑戦の中でも比較的大きな比重を占めているのが,法線マッピングを利用した局所照明エフェクトと,フォトンマッピングを利用した大域照明エフェクトの技術だ。

これらの要素のうち,法線マッピングについては,画面写真を見ればすぐに伝わってくる要素なのだけれど,フォトンマッピングについては,その効果が非常に分かり難いところがある。 Halo のように,極端な屋外や,薄暗い屋内などが舞台である場合,大域照明の効果はなかなか出難いように思える。しかも,全般的にギラギラとグロスの強いビジュアルイメージで統一されているものだから,大域照明の生み出す柔らかな照り返しなどは,まったくかき消されてしまうのではないかと思う。

例えば "ICO" のように,淡色系の壁面と複雑な建造物,それに強烈な太陽光などが加わると,間接照明の影響が強くなるために,大域照明の効果が非常に現れやすいシチュエーションとなる。逆の例では, Id Software が過去に大域照明を採用しながらも,それを敢えて捨てたという話がある。同社は Quake II においてラジオシティ法を採用したものの,結局,ぼんやりとした効果と長い計算時間しか得られなかったために, Quake III では単純なシャドウ処理に戻したという顛末だ。

いずれにしろ,あの Halo のギラギラしたイメージと大域照明の効果はミスマッチであるように思えてならない。個人的に大域照明を推していながらも,応用に対しては楽観できない理由が,この辺りに存在している。


Generic Programming (1)

2003-05-14

STL の創始者こと Alex Stepanov 氏のインタビュー記事をもう一度読み返してみた。

http://www.antiquark.com/escape/public_html/stepanovusa.html

それにしても,この人,よく読んでみると,かなり過激な発言をしていることが分かる。

Q: STL とジェネリックプログラミングは,既存の C++ のプログラミングスタイルから
の逸脱を果たしているように思えます。これらの概念は,ほぼ完全に SmallTalk から
派生されたもののように見えるのですが,どうでしょうか?

A: その通り。STL はオブジェクト指向では無い。オブジェクト指向などは,人工知能
と同じ種類の法螺話であると考えている。私は未だに,オブジェクト指向派の人間の手
から,興味の惹かれるコードが生み出された所を見たことが無い。
Q: Java は非常に新しい言語であるにも関わらず,テンプレートの機能を欠いているた
めに,ジェネリックプログラミングを不可能なものとしています。すべてはクラスでな
くてはならないのです。Java について,あなたはどのような考えを持っていますか?

A: Java は何ヶ月か触ってみたことがあるよ。しかし,作者らの予想に反して,私の中
で Java が流行ることは無かった。この言語からは,新しい識見が何も得られないんだ。
新しい言語から何も見識が得られないというのは,私の人生でも初めての経験だよ。
Java は,私が C++ で使うことのなかった機能(例えば,継承とか,仮想関数とか)を
保持していながらも,私が有用だと考えていた機能をすべて取り去ってしまっている。
もしかしたら,Java は成功するかもしれないよ − MS DOS がそうであったようにね。
商売にはなるのかもしれない。でも,そこに知的な価値は何も無いんだ。
(中略)
Java は明らかに MOP - "money oriented programming" の典型例だ。Java の開発
チーフが SGI に来たとき,彼は私にこう言ったんだ − 「アレックス,君はもっと金
のある所に行く必要がある」。しかし,私は金のある所になどは行きたくない。そうい
う所は,必ず,嫌な匂いがするんだ。

氏のインタビューを読んでいて強く感じるのは,氏の持つ数学的なバックグラウンドが, STL の設計思想に対して絶大な影響を与えているという印象だ。

氏は件のインタビューの中で,ジェネリックプログラミングのアナロジーとして数学の概念を引き合いに出している。数学の世界では,様々な種類の定理 (theory) が,同じ公理 (axiom) を出発点として導き出されている。

公理:演繹的理論の出発点として,証明なしに採用される命題。
定理:定義や公理に基づいて証明される数学上の命題。
<国語大辞典(新装版)(C) 小学館 1988>

数学とその応用の世界に存在する様々なモデルは,その多くが,ある部分で公理を共有している。これは,異なったモデルであっても同じような概念が適用できることを意味している。例えば,情報代数の導入によってデータ群を線形代数のように扱うことが可能となることや,関数解析によって関数を空間的に扱うことが可能となること,ラプラス変換を使えば微分方程式を代数演算によって解くことが可能となること,伝達関数から線形システム回路の解析と設計が可能となること……等々。 Stepanov 氏は,これこそが「抽象化」の概念であると主張している。

氏の挙げた例で言えば,「結合則が公理として成立すれば(つまり半群であれば)並列化が可能となる」という関係が存在する。これは,結合則によって以下のような入れ換えが可能であることを意味している。

f(f(f(w,x),y),z) = f(f(w,x),f(y,z))

この「アルゴリズム」に必要な条件は,ニ項演算 f が結合則を持つという事実だけだ。これと同じように,現実世界の様々なアルゴリズムには,共通して適用することのできる原則が存在するはずだと氏は指摘する。それを上手く見つけ出し,抽象概念として定義することができれば,非常に応用範囲の広いモジュールを設計することが可能となる。


STL では,例えばイテレータの存在が,この「公理」の部分に相当する。

http://www.sgi.com/tech/stl/Iterators.html

STL において,イテレータとは,ポインタの一般化された概念として表すことができる。イテレータには,単なる参照を意味する "Trivial Iterator" や,前進 (operator ++) を持つ "Forward Iterator",後退 (operator --) を併せ持つ "Bidirectional Iterator", 任意な移動が可能である "Random Access Iterator" 等が定義されている。これらの定義は,ジェネリックな領域における要求仕様として「コンセプト」と呼ばれている。この「コンセプト」こそが,数学での「公理」に相当する概念として存在するわけだ。

STL において二分探索を行うアルゴリズム "binary_search" は,コンセプトとして Forward Iterator と Strict Weak Ordering を要求する。 "Strict Weak Ordering" は「厳密弱順序」に基づいた比較演算を意味しており,これも STL のコンセプトの一種として定義されている。アルゴリズム "binary_search" は,これらのコンセプトに基づいて設計が行われており,コンセプトさえ正しく備えられていれば,その実態について何も問われることは無い。それが単なる int* であろうが,線形リストノードへの参照であろうが,一向に問題無く実装が導出されるということだ。


Q: つまり,「オブジェクトの中に(仮想的な)アルゴリズムを見つける」のではなく,
「アルゴリズムの中に(ジェネリックな)データ構造を見つける」と言うことができる
わけですね?

A: そうだ。常に出発点はアルゴリズムであるべきだ。

Generic Programming (2)

2003-05-15

では,我々はどのようにして,ジェネリックプログラミングの概念を役立てることが出来るのだろうか。僕が個人的に,ゲームプログラマにとって最も分かりやすい例ではないかと考えているのが, A* アルゴリズムの実装についての話だ。

http://www.policyalmanac.org/games/aStarTutorial.htm

http://www.gamasutra.com/features/20010314/pinter_01.htm

A* アルゴリズムは,ゲームプログラミングにおいて,一般に経路探索 (pathfinding) の手法として知られている。敵キャラクタが障害物を避けてプレイヤーへ近寄るには,どのような経路を選択したら良いのだろうか,とか,戦略ゲームのユニットが指定地点まで最短コストで移動するには……とか,そのようなものを決定する手法である。

しかし, A* は本来,経路探索だけのために存在するアルゴリズムではない。 A* は一般に,探索木(ゲーム木)に対する探索処理の最適化されたアルゴリズムとして用いることができる。

http://www.na.cse.nagoya-u.ac.jp/~reiji/lect/alg99/sec9-4.ht...

単純なバックトラック法に対し優先度評価 (heuristic estimation) を与えることによって,探索の最適化を行うというものだ。

従って, A* の応用範囲は単なる経路探索にとどまらない。チェスの思考ルーチンに適用することもできるし,戦略ゲームの CPU アルゴリズムとして利用することもできる。パズル問題の解法としても使えるし,もしかしたらデータ構造の最適化に応用することも可能かもしれない。

http://www.gamasutra.com/features/20000626/brockington_01.ht...

A* の実装に必要となる要素は,問題の「状態」を表すデータ構造と,状態の「評価」を行う手段の存在だ。状態の評価を行う方法は2種類必要で,1つは,現状の評価を行う関数 G ,もう1つは,現状から到達し得る最良の評価値を求める関数 H だ。

ここで,「状態」を表すデータ構造 State は,現状から移行し得るすべての状態を列挙するメソッド enum_trans と,同一演算子 "==" を持つものとする。ファンクタ GFunc と HFunc はそれぞれ関数 G, H に相当し, State::Value 型の値を返すものとする。

以上をコンセプトとして置いた場合,関数 a_star は,次のように書き出すことができるかもしれない。

tepmlate<typename State, typename Stack, typename GFunc, typename HFunc>
void a_star(const State& start, Stack& result)
{
...

この関数のユーザは,前述のコンセプトを満たすデータ構造として State と GFunc, HFunc をそれぞれ用意してやれば,その実態については何も問われずに,関数 a_star の適用を行うことができる。例えそれが,経路選択を行うためのセットであっても,戦略思考を行うためのセットであっても,何でも良いということだ。

これをオブジェクト指向的に解決するとなると,ライブラリ側に用意された抽象クラス AbstractState を継承して State を定義しなくてはならなかったり,各種評価関数や enum_trans を仮想関数として実装しなければならなかったり……という風に,様々な制約が付加されることになるだろうと思う。

ここで重要なのは,ジェネリックプログラミングによる実装が,データ構造をアルゴリズムから完全に引き剥がすことに成功しているということだ。これは,アルゴリズムのインタフェースが,真に抽象的な領域において定義されていることを意味している。オブジェクト指向では,その「オブジェクト指向性」ゆえに,アルゴリズムとデータ構造が同一の領域に混在してしまっていた。それ故に,抽象化を行うには仮想関数や多重継承の概念を導入する必要があったのだけれど,それも実のところ,付け焼き刃的な進化でしか無かったと言うことが出来るのかもしれない。


Generic Programming (3)

2003-05-16

Stepanov 氏のインタビューを読み直して,改めて感じるのは, C++ ジェネリックプログラミングと関数型プログラミングとの間に存在するスタイルの類似だ。 C++ 方式のジェネリクスでは,関数のファンクタ化とテンプレートを用いることによって,「関数をファーストクラスとして扱うことが出来ない」という C++ の弱点を補い,関数型プログラミング的なセマンティクスの導入を可能としている。また, STL や Boost に含まれる関数アダプタの機構は,関数型プログラミングにおける「高階関数」(あるいは「超関数」……つまり,関数を操作する関数のことを指す)の概念を連想させるものだ。

http://www.sato.kuis.kyoto-u.ac.jp/~igarashi/class/isle4/mlt...

事実, Stepanov 氏は過去に,関数型プログラミング言語を含む複数の言語において,ジェネリックなライブラリを作成した経歴があるようだ。 SGI の STL ページにある次のインタビュー記事には, C++ STL が設計されるに至るまでの長い道程が,実に詳細な形で語られている。

http://www.sgi.com/tech/stl/drdobbs-interview.html

ニューヨークの GE Research Center に勤務していた頃のことだ。我々は Tecton という
言語の開発を始めていた。この言語は,ジェネリック構造と呼ばれる形式的な型とプロパ
ティの集合を持っている。これをアルゴリズムと結び付けることによって,アルゴリズム
の記述が可能となるという,実に数学的な代物だった。我々はこの構造を用いて,各種操
作の代数的な定義を行うことが可能であることを認識するに至った。これを改良 (refine)
し,増強 (enrich) することによって,すべての物事が可能となるのだ。

これは興味深いアイデアであったものの,この研究が実用面での成果を導き出すことはな
かった。なぜなら,Tecton は関数的だったからだ。当時,我々は Backus 氏のアイデア
を信じていた。曰く「我々はプログラムをノイマン型から解放しなくてはならない。副作
用 (side effect) を用いてはならない」。しかしこのことは,副作用を必要とする種々
のアルゴリズムを制御する術を,自ら制限してしまっていたのだ。

結局,関数型プログラミングに偏重したスタイルは失敗に終わったものの,そこでの経験が後の設計方針に影響を及ぼしているという事実は興味深い。それに,僕は今まで関数型プログラミングに接した経験がほとんど無い。もし,関数型プログラミングの手法が「コンポーネント空間」を構成する1つの軸だと考えるならば,これを欠かしていることは,表現の幅を大きく失っていることに他ならない。

ちなみに,「コンポーネント空間」とは,氏が STL の設計概念について述べる際に,よく用いられる表現の1つだ。

http://www.byte.com/art/9510/sec12/art3.htm

人々は,過去 25 年間に渡り,すべてのプログラムを単一の原始コンセプトへと縮小する
ことによって,プログラミングに変革を起こそうとしてきた。その1つの例が関数型プロ
グラミングである。関数型プログラミングとは,すべてを関数によって構成するものであ
り,状態やアドレスの概念,それに副作用などはタブーとされた。次に到来したのがオブ
ジェクト指向であり,今度は関数がタブーとなった。すべては状態を伴ったオブジェクト
でなくてはならないのだ。

STL は,関数型プログラミングとオブジェクト指向の影響を大きく受けている。しかし,
STL は単一のパラダイムによるライブラリではない。 STL は,ノイマン型コンピュータ
のプログラムにおける,全般用途的なライブラリなのだ。

STL はコンポーネント空間の直交分解という考えに基づいている。例えば,「配列」と
「二分探索」は,単一の基本概念へと縮小することができない。これら2つは完全に異な
る要素だ。配列はデータ構造,つまりデータを格納するコンポーネントである。二分探索
はアルゴリズム,つまりデータに対して演算を行うコンポーネントである。データ構造が
適当なアクセス手段を提供する限り,二分探索アルゴリズムをこれに対して適用すること
ができる。配列と二分探索の間に存在する基本概念の違いを考慮するだけで,効果的でエ
レガントな解決法が得られるということだ。

everything2.com の解説によれば,「関数型プログラミング」とは,「コマンドの実行」よりも「関数の評価」に重きを置くスタイルのことである,と定義されている。

http://everything2.com/index.pl?node=Functional%20programmin...

単に関数がファーストクラスだからと言って,その言語が関数型か,と判断してはならないようだ。実際に関数型言語の例を見てみると,これが実に数学的な思考のもとに作られたものであることがわかる。関数の再帰的な定義によってロジックが組み上げられていく様は,まるで数学の教科書を覗いているかのような雰囲気であり,手続き型言語には決して見られない論理的な堅さが感じられる。

http://a414s1.it.nanzan-u.ac.jp/smlbook/smlwww/index.html

http://a414s1.it.nanzan-u.ac.jp/smlbook/smlbook.pdf

もう少し具体的な定義を行うならば,関数型プログラミングとは,ラムダ計算 (lambda calculus) をベースとして構築された言語として見ることができるかもしれない。

http://members.tripod.co.jp/nbz/ref/lambda.html

ここから先は,もうちょっと数学的な知識が無いと踏み込めなさそうだ……。ともかく,今までまったく注意を払っていなかった関数型プログラミングというものに対して,もう少し興味を持ってみようと思っている。


Generic Programming (4)

2003-05-17

これらの Stepanov 氏の言説によって明らかにされているのは, STL 及びジェネリックプログラミングの手法が,これまでのプログラミング体系を複数内包した,いわゆる「マルチパラダイム」的な存在であるということだ。これは, Stroustrup 氏の行った C++ の設計思想から影響を受けたものであり,またこれとは逆に, STL 自身が C++ の設計に対して影響を与えた部分(例えば部分特殊化 - "partial specialization" の機能など)も存在することが,文献の中で述べられている。

私はベル研究所において C++ テンプレートの設計に関する様々な協議に参加した。そこ
で私は Bjarne (Stroustrup) に対して,C++ テンプレートをできうる限り Ada のジェネ
リクスへ近づけるよう,猛烈に主張を行った。そこで私があまりにも猛烈に主張を行った
ものだから,彼はそれに対して反抗してしまったのかもしれない。私は他の人々と同様に,
テンプレートクラスだけでなくテンプレート関数を持つことの重要性を認識していたもの
の,テンプレート関数は Ada と同じく明示的にインスタンス化されるべきだと考えてい
た。Bjarne は私の言うことには耳を貸さずに,テンプレート関数を暗黙にインスタンス
化するよう設計を行った。しかし実は,この技術こそが私の後の仕事にとって決定的な要
素であり,Ada ではできなかったことを可能とする技術だったのだ。私は,この設計が
Bjarne の行った素晴らしい仕事の一片であると考えている。そして,彼が私のアドバイ
スに従わなかったことを,とても有り難く思っている。

C++ / STL におけるマルチパラダイムとは,一体どのようにして導き出された結論なのだろうか。一見して,マルチパラダイムは安直で危険な思想のように思える。複数の体系を内包することによって,その基礎原理を曖昧なものとしてしまうように感じられるからだ。しかし,氏の持つ優れた学術的見識と,多くのプログラミング言語から得られた豊富なバックグラウンドは,その困難な仕事の実現を可能としたようだ。

(1985 年に GE Research へ戻ってから)私は Ada 用のライブラリを構築するにあたって
Dave Musser と共同作業を行うことになった。これは実に重要な仕事だった。なぜなら,
Scheme のような「動的型付け」言語から,Ada のような「強い型付け」言語に移行した
ことが,強い型付けの重要性を認識させることになったからだ。人々は強い型付けがエラー
発見に役立つと考えている。しかし私は,強い型付けが Ada の generics において設計
手法ともなり得ることを発見した。バグを見つけるための道具であるばかりでなく,思考
のための道具でもあるということだ。そしてこの仕事は,「コンポーネント空間における
直交分解法」というアイデアを導き出すこととなった。
何ゆえに C という言語が優れているのか,ここで少し考えてみるとしよう。一般に C は
「ハック」であり,これが成功したのは,単に Unix が C によって書かれていたためだ
と考えられている。しかし,私はこれに同意しかねる。
(中略)
Dennis Ritchie の非凡なる才能を反映した結果である C 言語は,この 30 年間に渡って
進化し続けてきたコンピュータアーキテクチャの最小モデルを提供している。C は,いわ
ゆる「クイック・ハック」ではない。コンピュータは,あらゆる種類の問題を扱うべく進
化し続ける。 C はそのようなコンピュータの最小モデルであり,様々な領域のあらゆる
問題を解決するにあたって,非常に強力で効果的な言語となったのである。

単一的に凝り固まった解を安易に提示するのではなく,様々な見地から最適と呼べるソリューションを導き出す手腕は,氏のように豊富な経験を持つ人物でなければ持ち得なかったであろうと思う。そしてそれこそが,それまでの議論に欠けていた要素なのだろうと思う。


なんだか引用ばかりになってしまったけれど,つまり, Stepanov 氏の言説の中に,それだけ重要な要素が含まれているということだ。 STL や C++ を一通り修めた後にこれらの文献に目を通せば,その奥に潜む深い思想を垣間見ることができるだろうと思う。そして,今までよりもほんの少し C++ が許せるようになっているはずだ。

最後に, STL を構成する重要な概念である「複雑性」 (complexity) と「コンセプト」 (concept) について触れた言葉を引用して締めくくりたいと思う。

ある演算の「複雑性」は実装に依存する部分であり,抽象概念においては複雑性を無視す
ることができる,という仮定が一般に用いられている。しかし今,私がジェネリックプロ
グラミングの核心として理解していることは,複雑性とは演算自身と結び付けられるべき
であるということだ。
(中略)
我々はインタフェースから実装を分離しなければならないが,その際に複雑性を完全に無
視することがあってはならない。複雑性はモジュールとその利用者の間において暗黙の約
束として成立しなければならない。抽象化されたデータ型という概念を導入する理由とは,
それがソフトウェアモジュールの可換性を実現するからであったはずだ。もし私があるモ
ジュールを,同じ機能持つが複雑度の異なるモジュールと取り換えたとしたら,そのコード
の利用者は不愉快な驚きを得ることになるだろう。私は彼に対して,データ抽象化に関す
る事実をいくらでも述べることができるが,それでも彼はそのコードを使おうと思わない
だろう。複雑性の提示とは,インタフェースの一部であるべきなのだ。
私は以前,こんな願望を述べたことがある。一般的なパラダイムに適合する抽象的なコン
ポーネントを集め,それをプログラマの標準的なリポジトリとして持つことができたなら
ば,と。
(中略)
そこでのゴールとは,ソフトウェア・エンジニアリングを,高等芸能から学問へと移行さ
せることだ。それには,基礎的なコンセプトと,それらのコンセプトを支配する法則につ
いて分類する術が必要となる。その術とは,既によく理解されており,人に教えることが
可能であり,すべてのプログラマが知り得るものでなければならない(たとえ彼らが正確
に理解していなくとも,だ)。
例えば,多くの人々は「交換則」を耳にしたことがなくとも算術を知っている。高校を卒
業した人であれば,2+5 が 5+2 と同じであることを知っているはずだ。しかし彼らのす
べてが,加算の持つ交換則の性質について把握しているわけではない。
私の望みは,多くのプログラマが基礎演算に関する意味論 (semantic) を習得することだ。
「代入」が意味するものとは? 「同一性」が意味するものとは? データ構造を構築する
術とは?
(中略)
そこで STL は「コンセプト」を扱う。例えば,イテレータとは何だろうか? それはクラ
スではないし,型でもない。「コンセプト」なのだ。これは C++ において言語的に具体
化されていないものだ。しかし,それについて語る言葉を皆は持っている。それを改良し,
プログラム的な方法論において「クラス」という形で構築することが可能であるはずだ。

おまけ……

私は,この方法論にもっと適した言語を設計することが可能であると考えている。それは,
C や C++ と同じぐらい効率的なものであり,かつ,コンピュータの本質に近いものになる
だろうと確信している。極度に抽象化された事象を扱う能力を持つ一方で,コンピュータ
の本質に近い言語を構築することが可能であるということだ。私はこの抽象性を,コン
ピュータとの間に生じるギャップを広げることなく,C++ より優れたものにすることがで
きると考えている。

ジェネリックプログラミングは言語の研究に対して影響を与えることだろう。そして将来,
この方法論に適し,利便性に優れ,かつ実用的な言語を得ることになるのだ。

これらのことから,私が次の仕事として企てていることが推定できるのではないだろうか。

Haskell

2003-05-18

普通の休日。散歩してみたり,本屋で立ち読みしてみたり。

ここ数日に渡って,関数型プログラミングについて調べる機会が何度かあった。これも何かの縁だから,ひとつ関数型言語でも触ってみようかと思い,試しに Haskell を遊んでみることにした。

http://www.haskell.org/

数ある関数型言語の中から Haskell を選んだのは,これが現状で最も「ピュア」な関数型言語であるとの指摘が各所でなされていたからだ。どうせ本気で利用する気は無いんだし,試すだけなら極端なものを試してみるのが良いだろうと思う。

処理系は "Hugs" を使うことにした。 Haskell 処理系の中では最も有名なもののようだ。

http://www.haskell.org/hugs/

ソースのアーカイブを入手し,インストラクションに従ってビルドを行う。 Linux や Cygwin なら問題無くビルドできるはずだ。

haskell.org のリンクから適当なチュートリアルを入手して,一から手習いを始めてみている。しかし,こりゃあすごいもんだなあ……。ちっとも要領が得られないのだ。

http://www.di.uminho.pt/afp98/PAPERS/Tutorial.ps

ftp://ftp.geoinfo.tuwien.ac.at/navratil/HaskellTutorial.pdf

僕は今まで,何らかのプログラミング言語を一通り習得した人であれば,二つ目以降の言語は比較的速やかに習得できるものと考えていた。しかし,その法則は今回の経験によって完全に覆されることになりそうだ。自分のプログラミングに対する思考パターンと設計能力が,いわゆる手続き型言語のそれへと完全に限定されてしまっていたことを認識するに至った。


散歩中に立ち寄った本屋で,思わず衝動買いをしてしまった。 John Barrow の「天空のパイ」だ。

http://www.msz.co.jp/titles/7018.html

とても恥ずかしいことなのだけれど, Stepanov 氏の記事を読んでいる間に,自分の数学的見識の無さに対して愛想が尽きてしまった。何か数学関連の本を読んで,少しでも知識を補いたい……。そんなことを考えながら書店を徘徊していると,いつの間にかこの本を手に取っていた。数学の本を探していたはずなのに,何故か縦書きの本を買ってしまっている所が,まったくもって駄目だと思う。専門書を買うつもりでブルーバックスを買ってしまっているような勘違い具合だ。

そして,なぜかそのまま勢いに乗ってしまい,ついでに "Exceptional C++" も買う羽目になってしまった。ああ,もう何がなんだか……。

http://www.pearsoned.co.jp/washo/prog/wa_pro34-j.html

こんな一度に本を購入して読み切れるはずがない。また積読が増えてしまう……。そう言えば,まだ「コモンズ」も読み終わってないんだよね。うう,駄目かも……。


Half-Life 2

2003-05-19

E3 の遠征隊が帰国した。お仕事ご苦労様。お土産たくさん,嬉しいな……。

とりあえず真っ先に手を付けてみたのが "Jak II" の体験版だった。

http://www.gamespot.com/ps2/action/jak2/index.html

US リージョン用なので普通の PS2 では動かない。職場の開発機を使って動かしてみることにした。

前作は徒手空拳が主体のジャンプアクションであったのに対して,近作は銃火器系を備えた撃ちまくりアクションへと変質を遂げているようだ。銃を撃ちまくる行為には単純な面白さがある。 "Jak" も結局はそこに落ち着いてしまうのか,という,少し残念な気持ちはあるものの,滑らかな操作性のジャンプアクションとシューティングという組み合わせは,意外と相性の良いもののように感じられた。

少し気になったのは,昔の全方位スクロールシューティングにおけるジレンマというか,「敵に狙いを定めるには,敵に対して突っ込まなくてはならない」ということから「一度距離を詰められてしまうと反撃が不可能となる」という死にパターンが発生してしまっているように感じられたことだ。前作よりもステージの一方向性が希薄になっていることが,敵に背後を取られる危険性を高め,死にパターンを発生させる状況を増長してしまっているように思える。もっとも,そういう場合は近接攻撃を使えばいいんだろうけど,一旦トリガーに指をかけてしまうと,こう,なかなか……ね。

ボスコニアンばりにケツから弾吐くってわけにも行かないだろうしなあ……。今のままでも,アクションゲームとしての面白さは十分に伝わってくる状態ではあるのだけれども,果たして最終的にはどのような形へまとめてくるんだろうかと,やや不安を抱えながら楽しみにしている状況だ。


Halo2 や MGS3 など,色々と見所の豊富な E3 だったのだけれど,最後に登場した "Half-Life 2" のムービーには,正直なところ,度肝を抜かされてしまった。これは凄い……。

http://38.118.133.70/gt_vault/t_halflife2_e3_2k3_gameplay.wm...

プライベートカンファレンスだか何だかの映像が流出したもののようだ。前半部分に見られる,いかにも技術デモ的な部分は敢えて無視するとして(ここのシステムだけとって見ても,かなり強力なものではあるんだけれど),中盤以降……特に後半の,実際のゲームプレイを映し出したと思われる部分に深い感銘を受けた。これは恐らく開発中の映像であろうから,少し勘違いをしている可能性もあるんだけれど,そこは敢えて受け取ったままの印象を元にしてみようと思う。

このゲームにおいて驚くべきことは,カットシーンとしての演出がまったく存在しないことだ。すべての演出がゲーム内の出来事として完結している。ダイナミックな環境の扱いを可能とする Half-Life 2 エンジンの強力な機能性は,デザイナの望むすべての事象を,ゲーム内の空間において実現することを可能としている。 NPC 達は優れた AI 処理によって自然な行動様式を持ち(ここは Half-Life シリーズの伝統だ),ゲーム空間を構成するもう1人のアバターとしてそこに存在する。彼等(とプレイヤー)は自由にゲーム内の環境に触れ,それを動かし,利用し,破壊する。従来はカットシーン……つまり,ゲーム進行における「特異点」として演出しなければならなかったものを,すべて実際のゲームプレイとして体験することが可能となっている。

戦火に荒らされた東ヨーロッパを連想させる街並みの奥の方から,突如として轟音が聞こえてくる。「スパイダーだ!」 叫びながら逃げてくる数人の男と,巻き上がる砂埃。そして,その奥から現れる巨大モンスターの影。必死に応戦する人間達と,それを支援するプレイヤー。圧倒的な火力を誇る化け物の攻撃に街は蹂躙され,人々は一撃のもとに串刺しにされていく。一人称視点から見上げるスパイダーの存在は強い威圧感を感じさせるものであり,本物の恐怖を連想させる。 Half-Life 2 では,これらをすべてゲームプレイの一部として体験することが可能なのだ。

外見せ用のムービーということで,少し「出来過ぎている」感は否めない。しかし,ここは敢えて,このゲームを信用してみようと思っている。本当にこのゲームを楽しむことができたとしたら,この臨場感を味わうことができたとしたら,それはとても素晴らしいものであるはずだ。


boost::pool

2003-05-20

普通にお仕事の一日。ううむ,そろそろコンパイラの新しいバージョンが欲しいなあ……。


以前,メモリ管理関連の話題で Boost Pool Library に注目したことがある。

http://boost.org/libs/pool/doc/

これが,実際に利用してみると,色々と細かな問題を抱えているライブラリであることが分かってきた。

まず問題となるのが,実に些細なバグが残された状態で放置されているということだ。

http://lists.boost.org/MailArchives/boost/msg37427.php

もしかしたら既に修正されているかもしれないと思い,リポジトリ内の最新バージョンを参照してみたのだけれど,そのような気配はまるで感じられなかった。多くのファイルが半年以上放置されている状態だ。ううむ,自前で修正するのは簡単なことだけれど,訳の分からぬまま手を加えてしまうのも何だか腑に落ちない。このバグは現在どういう扱いになっていて,将来的にどう処理される予定なのだろうか。その辺りの事情が分かっていれば,迷うことも無いのだろうけど……。

詳しく調査を進めていくと,他にも色々とクセを抱えたライブラリであることが分かってきた。アイデア自体は決して悪くないのだけれど,細かい所をつつきだすと,さすがにボロが出てしまうようだ。特に singleton_pool を使ってしまうと,メモリの明示的な開放がほぼ不可能となってしまう点が,歯痒く感じられる。 pool_allocator も内部で singleton を呼んでいるので,迂闊に利用することができない。ううむ……。

結論を言えば,このライブラリをそのまま利用するのではなく,修正や多少のカスタマイズを適用した上で利用するのが良さそうだ。 pool クラス自体は前述のバグを修正するだけでほぼ問題無く利用できるようになるから,あとは細かい不満点をちくちくと修正するだけだ。少し面倒に感じられるかもしれないけれど,すべてを自作するのと比べれば,随分楽な作業量で済ますことができるだろうと思う。


正味な話, Boost は全般的に,細かい所で気になる点が多い。提供される機能に対して払うコストが明らかに見合っていないモジュールや,今回のケースのように,中途半端な状態で枯れてしまっているモジュールなど,少なからず存在する。それにも関わらずモジュールは増える一方であり,かなり混沌とした雰囲気を呈してしまっている。

構文パーサの "Spirit" や,線形代数ライブラリの "uBlas" など,元は独立したライブラリとして開発されていたものだと記憶しているのだけれど,これが一体どのような経緯で Boost に統合されることになったのか,僕にはまったく見当も付かない。

http://boost.org/libs/spirit/index.html

http://boost.org/libs/numeric/ublas/doc/index.htm

ともかく, Boost を STL 並に「枯れたライブラリ」だと受け取ってしまうのは危険な考えだ。各モジュールの複雑度や安定性を見極めた上で,利用するか否かの取捨選択を行うのが良いだろうと思う。


ccache / distcc

2003-05-21

雑用サーバの HDD がクラッシュしたので,換装作業を行うことにした。ううむ,なんだか知らないけれど,このビルに来てからというもの,マシンのクラッシュ率が高くなっているような気がするんだよな……。

インストール作業の間,自分のマシンが手すきになるので,その間に 4gamer.net に掲載されていた Half-Life 2 の高画質版ムービーをダウンロードしておくことにした。

http://www.4gamer.net:880/HALF-LIFE2.lzh

さすがに 260MB も食っているだけあって,こちらの方が画質が数段良いようだ。

うむむ……,これ,「ストライダー!」って言ってるよね。スパイダーじゃねえっつうの……。

http://www.gamers.com/game/273625

ええと,まあ,こんな調子なんで,字幕の無い洋ゲーは非常に辛い。字幕が付いていれば,少なくとも要点ぐらい掴むことができるのだけれど,字幕無しでミッション内容とか告げられた日にゃ,どうにも途方に暮れてしまう。うう……。


以前, shinichiro さんが gcc-pch 関連の話題を挙げた際に, ccache というツールの存在について軽く触れていた。これがなかなか面白そうなツールなので,少し試してみることにした。

http://ccache.samba.org/

ccahce は Samba プロジェクトの副産物として生み出されたツールだ。同プロジェクトのビルドファームにおいて定期的に実行されているリビルド処理を効率化するために編み出されたものであるらしい。

http://build.samba.org/

ccache は,その名前が表しているように, C ソースのコンパイル処理に対してキャッシュを設けるというものだ。このツールは gcc のフロントエンドとして機能し,入力に用いられたソースファイルと,そこから出力されたオブジェクトファイルの対応を記録し,キャッシュへ格納するようになっている。そして,過去にコンパイルしたことのあるソースが入力されると,実際にはコンパイルを行わずに,キャッシュ内に格納されたオブジェクトファイルを取り出してくる。こうすることにより,コンパイル時間の大幅な削減が実現できるというわけだ。

「過去にコンパイルしたことのあるソース」という判定を正確に行うために,少しトリッキーな手順が用いられている。単純にソースのみで判定を行ってしまうと,インクルードファイルの依存関係の解決が難しくなってしまうため,実際にはプリプロセス済みファイル (.i) が判定対象として用いられる。また,コンパイルオプションによっても生成結果は変化するため,これも判定基準として加味しなければならない。それらをすべて含めた上で, MD4 アルゴリズムを用いたハッシュインデクスを取得し,それをキーとしてキャッシュ処理を行う……という手順になっているようだ。


結局のところ,ソースが変化してしまえば再コンパイルとなってしまうのは同じことなので,個人環境においてはなかなか効果が得られ難いかもしれない。しかし,複数のユーザが共同で作業を行う場合においては,かなりの効果を上げられる可能性がある。キャッシュを他人と共有すれば,他人がコンパイルした結果をそのまま流用することが可能となるからだ。

例えば,リポジトリから取り出してきた最新状態のソーススイートなどは,それをコミットしたユーザが既にコンパイルを済ませているという見込みがある(というか,そうあって然るべきだ)。そのような場合に ccache を利用していると,二人目以降のユーザは一度もコンパイルを行うことなくビルドを完了させることができる。通常ならば数分間も待たされるはずのフルビルド作業が,瞬く間に終わってしまうのは非常に爽快だ。マシンリソースを有効活用するという点から見ても大きなメリットがあるだろうと思う。


Samba には,もう1つ注目すべき副産物がある。分散コンパイル支援ツール "distcc" だ。

http://distcc.samba.org/

distcc は ccache によく似たアイデアを持つツールだ。このツールは,親マシンにおいてプリプロセス処理までを行い,その結果として得られた中間生成ファイルを子マシンに分配する。実際のコンパイル処理は子マシンにおいて行われ,その結果として得られたオブジェクトファイルは親マシンへと戻される。このように,かなり簡単な原理で動いているツールなのだけれど,それが結構ソツ無く作られており,手軽に強力な分散コンパイル環境を構築することが可能となっている。

distcc の良さは,プリプロセス処理までを親マシンで行うことにある。プリプロセスの段階でインクルードファイルの処理は完了しているため,子マシン側にソースやライブラリの類を入れておく必要はまったく無い。ただコンパイラのみが辛うじて動けばそれで良いという,非常に緩い条件のもとで分散コンパイルを実現することができるわけだ。

実際に分散コンパイル環境を構築したことのある人なら分かると思うのだけれど,この条件の緩さは大変に有り難いものだ。リモートシェル等を使ってアドホックな分散環境を構築する場合,子マシン側にも完全な開発環境を用意しておく必要があった。コンパイラや各種ライブラリをインストールして,アカウントをメンバー分作成して,作業ディレクトリをネットワーク共有して,リモート制御に必要な設定を行って……等々,とにかく管理面での煩わしさが最大の難点であったと思う。

distcc を使えば,これらの問題に頭を悩ますこと無く,即興に分散コンパイル環境を構築することが可能となる。これは飛躍的な進化だ。少なくとも僕にはそう感じられる。

注意深くクロス環境を用意すれば, Linux ホストと Windows ホストを連携させることも可能であるはずだ。どこかに余ったマシンが放置されているならば,電気代と相談しつつ,再利用を試みるのも面白いかもしれない。


Using SSE in GCC (1)

2003-05-22

最近, PC 上でベクトルの演算を行うコードを書く際に SSE を利用する試みを始めている。

http://homepage1.nifty.com/herumi/adv/adv50.html

一応, GCC は SSE をいくつかの形式でサポートしている。最も単純なのはインラインアセンブラを使って記述する方法であり,他にはビルトイン関数を利用する方法や,内蔵関数 (intrinsic function) を利用する方法などが用意されている。

http://gcc.gnu.org/onlinedocs/gcc/X86-Built-in-Functions.htm...

最もコンパイラとの親和性が高いのは内蔵関数を利用する方法だ。内蔵関数は xmmintrin.h 内に定義されており,これをインクルードすることでソース内での利用が可能となる。

http://savannah.gnu.org/cgi-bin/viewcvs/gcc/gcc/gcc/config/i...

具体的な利用例は以下のようになる。

#include <stdio.h>
#include <xmmintrin.h>

int main(void)
{
    __m128 x = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
    __m128 y = _mm_set_ps(1.1f, 1.2f, 1.3f, 1.4f);
    __m128 z = _mm_add_ss(x, y);

    float f[4];

    _mm_storeu_ps(f, z);

    printf("%f %f %f %f¥n", f[0], f[1], f[2], f[3]);

    return 0;
}

"__m128" は 128 bit 長の変数型であり,これが SSE の xmm レジスタに対応するものとイメージすれば良いと思う。あとは,まあ,見た目通りの機能を持つ関数群だ。

本来ならばこれで第一歩目を踏み出すことができるはずなのだけれど,残念ながらこのサンプルコードは正常に動いてくれない。恐らく未定義命令を発行して停止してしまうことだろうと思う。もしまぐれで動いたとしても,もっと複雑な応用を繰り返すうちに同じ現象へと突き当たってしまうはずだ。

とりあえず,これを次のように書き換えてから,最適化を有効にしてコンパイルし直すと,辛うじて動かすことが可能となる。

static __m128 x, y, z;

void test(float* f)
{
    x = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f);
    y = _mm_set_ps(1.1f, 1.2f, 1.3f, 1.4f);
    z = _mm_add_ss(x, y);

    _mm_storeu_ps(f, z);
}

int main(void)
{
    float f[4];
    test(f);
    printf("%f %f %f %f¥n", f[0], f[1], f[2], f[3]);
    return 0;
}

前者の例がコケてしまった理由は, SSE の各命令が 16 byte 境界整合を要求することと関連している。 SSE 命令は,非整合アクセス命令である loadups と storeups を除き,すべての命令がメモリ参照のターゲットアドレスとして 16 の倍数を要求する。 RISC プロセッサのようなものだと考えればいいかもしれない。

GCC において変数の境界整合をとるには "aligned" 属性を用いるのがセオリーだ。しかし, GCC における aligned 属性の指定は,確実にそれが適用されるという保証が無く,最終的な結果はターゲットにおけるコンパイラとリンカの仕様に依存するものと定義されている。

http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html

つまり,実際にはコンパイラに与える「ヒント」程度の意味合いしか含まれていないわけだ。現に i386 ターゲットの GCC においては,データ領域については要求通りの整合配置が行われるものの,スタックに関しては 4 byte までの整合しかとられないという仕様になってしまっているらしい。

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=3299

また,この仕様に加えて,メイン関数のスタックが確実に非整合となる現象も報告されている。

http://gcc.gnu.org/ml/gcc/2002-07/msg00453.html

以上の関連する事実から,前者のコードでは,各変数の境界整合がとられていないことがクラッシュの原因となっていることが推測できる。これに対して後者のコードでは,各変数をデータ領域へと格納し, SSE 操作をメインとは別の関数で行うようにしたため,諸問題の解決が可能となっている。ただこのコードも,最適化を無効にすると落ちてしまう。内蔵関数がインライン展開されない限り,引数渡しの部分でスタックが用いられてしまうためだ。


……と,ここまで見れば明らかなように,内蔵関数を用いる方法が上手くいく見込みはまったく無い。また,この問題は長らく放置されている状態であり,近い将来に修正される見込みもほとんど無いように感じられる。 xmmintrin.h なんていう大層なヘッダファイルが用意されているわりには,それがほとんど無価値なものとなってしまっているのは,一体どういう状況なんだろうかと不思議に思う。

ビルトイン命令を用いる方法も,やはり同様に上手くいかない。ビルトイン命令は値渡しのインタフェースとして __mm128 相当のものを利用するから,結局同じ問題でつまづいてしまうわけだ。

では, GCC において SSE を利用することができないのかというと,必ずしもそうとは言い切れない。まだラストリゾートとしてのインラインアセンブラが残されているからだ。それも相当に注意深く用いなければならないのだけれど,やってやれないことは無いレベルのものだ。

今後はその辺りの問題について,もう少し探ってみたいと思う。


Using SSE in GCC (2)

2003-05-23

残念ながら現状では, GCC に備わっている各種の SSE 拡張機能が,ほとんど当てにならないものであることが分っている。残された手段は,インラインアセンブラを用いてこれを直に実装する方法だけだ。

ところで, GCC のインラインアセンブラは,他のコンパイラの持つそれと比較すると,幾分複雑な構造であるように見える。

http://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html

おそらく移植性を高める上でこのような工夫が必要となったのだろうと思う。基本的に GCC はインラインアセンブリ文の内容を解釈することができないので,値の受け渡しや各操作における副作用等を,ユーザが明示的に指定してやる必要があるということだ。


まず,値を格納するためのクラスを適当に設計する。 x86 GCC における境界整合が信用ならないことは既に分かっているので,もはや小細工を用意する必要は無い。安直に "float[4]" を使ってしまって構わないだろうと思う。

struct vector
{
    float c[4];

    vector()
    {
    }

    vector(float x, float y, float z, float w)
    {
        c[0] = x; c[1] = y; c[2] = z; c[3] = w;
    }
};

例として,このクラスに対応する加算操作 (operator +) を実装してみる。引数は非整合 (unaligned) の状態で渡されることが予想されるから, movups 命令で確実にロードを行ってから加算を行い,再び movups 命令で結果を格納する必要がある。

typedef int m128 __attribute__ ((mode(V4SF)));

inline vector operator + (const vector& _x, const vector& _y)
{
    m128 x, y;
    vector res;

    asm (" movups %1, %0" : "=x"(x) : "m"(_x));
    asm (" movups %1, %0" : "=x"(y) : "m"(_y));
    asm (" addps  %2, %0" : "=x"(x) : "0"(x), "x"(y));
    asm (" movups %1, %0" : "=m"(res) : "x"(x));

    return res;
}

ここでは,できるだけ GCC の作法に忠実な記述を行ってみた。例えば,レジスタ名を直接に用いるのではなく,プレースホルダとしての変数を介して参照している。また,処理を一連のアセンブリ文として書くのではなく,1命令毎に分割して記述するようにしている。このような記述を行うことによってコンパイラとの親和性を高め,最適化の可能性を広げることが可能となる。特にインライン展開されることを前提とした関数では,このような配慮が一定の効果を上げることがある。

上のコードは,以前に述べたような理由から x と y が境界整合されないと分かっているにも関わらず,一応正常に動かすことができる。変数 x, y はレジスタの形でしか参照していないため,コンパイラが余計なコードを差し挟まない限り,非整合アクセスでクラッシュすることも無いはずだ。

ただし,最適化の設定を無効にすると,コンパイラはこれを律儀にスタックに格納しようとするため,不正な非整合アクセスが発生してしまう。そもそも,境界整合が保証されないことが分かっているなら movups を使うようにすればいいと思うのだけれど,何故か GCC は movaps を使いたがるんだよな。むむむ……。

また,たとえ最適化がかけられているとしても,何らかの要因によって xmm レジスタのスタック退避が行われる可能性は十分に存在する。やはり,現段階で本当に安全なコードを実現するには,一連のアセンブリ文として記述してしまうしか方法は無いようだ。

inline vector operator + (const vector& x, const vector& y)
{
    vector res;

    asm (" movups  %1,     %%xmm0 ¥n"
         " movups  %2,     %%xmm1 ¥n"
         " addps   %%xmm1, %%xmm0 ¥n"
         " movups  %%xmm0, %0     ¥n"
         : "=m"(res) : "m"(x), "m"(y)
         : "xmm0", "xmm1");

    return res;
}

ここまで直截的な記述を行えば,コンパイラの余計な介入を差し挟むこと無く処理を行うことが可能となる。正直なところ,本来ならば movaps と addps の2ステップで済ますことのできるはずの処理が,ここまで回りくどい記述になってしまうとは思いもよらなかった。非常に歯痒いことだけれど,この辺りが現時点における GCC の限界だと把握しておくのが妥当だろうと思う。


Using SSE in GCC (3)

2003-05-24

こうして行った SSE 化が,どの程度パフォーマンスにインパクトを与えるものなのか,検証してみることにした。

まずは単純なショートループで計測を行ってみる。

boost::timer t;

for (int itr = 0; itr < 1000 * 10; itr++)
{
    boost::array<Vector, 1000> x;
    boost::array<Vector, 1000> y;
    boost::array<Vector, 1000> z;

    transform(x.begin(), x.end(), y.begin(), z.begin(), plus<Vector>());
}

cout << t.elapsed() << endl;

長い配列に対して加算を繰り返すだけのサンプルだ。これを条件を変えてコンパイルし,得られた結果を比較してみる。

FPU math  2.1771 [sec]
SSE math  1.2057 [sec]
SSE       0.8792 [sec]

ここで, "FPU math" は通常の手順で生成されたバージョンを指し, "SSE math" はコンパイルオプシ