小さなブログ

アセンブリ再入門

いま仕事で久々にアセンブリ言語を使っている。中高生のとき以来、数十年ぶり。当時はモトローラ68000だったが、今度はARM Cortex-M7。ターゲットはプレイデートだ。

プレイデートのグラフィックは、昔風にいうと水平型ビットマップ画面が1枚のみ。GPUもハードウェアスプライト/BGもなく、CPUでフレームバッファーにピクセルを書き込むだけ、との極めてシンプルな構成だ。もちろん、ビットマップ描画やソフトウェアスプライトなどの各種描画APIが用意されてはいる。しかし、そうしたAPIは汎用性のための無駄をはらむのが常だし、有り体にいえば性能がいまひとつで、これにゲームの限界を決められてしまうのは面白くない。そこで、自前の描画機能をアセンブリ言語で実装することにした。

もうひとつの用途は、3D関連の計算。GPUがない以上、その膨大な計算はCPUでこなすしかない。専門的な詳細は省くが、この手の処理にはパイプラインの最適化がかなり効く。しかしコンパイラーはそこまで細かく面倒を見てくれないため、手作業で、つまりアセンブリ言語で緻密に最適化しようというわけ。

などといった事情は本当だが、動機の半分はARMのアセンブリをぜひとも書いてみたかっただけ。ARM自体は広く普及しており、仕事で触れる機会は過去何度もありながら、アセンブリ言語を強いて用いるべき状況はなかった。よほどハードウェア寄りの分野でないかぎり、どんな組織も、多少の高速化より生産性・保守性を優先するのが常識だから。

Cortex-Mファミリーの命令セットは、ARMの短縮版であるThumb。便利な条件実行機能こそ省かれているものの、ゆえに68000と大差ない感覚で書くことができ、(3オペランドが便利な一方、メモリーに対する演算ができない、といったRISCらしい制約に戸惑ったりしつつ)自分のような出戻りにも扱いやすい。

まずは目当ての描画機能から、基本となるラインルーチンを実装してみた。あれこれ最適化を試み、実機における現時点の速度はAPI・drawLine関数の17倍超に達している(機能が異なるため公平な比較ではないが、ほかに比べようがない)。実践して感じたのは、最適化のための開発スタイルがいわば「個人技」から「チームワーク」に変じていること。

かつて68000を使っていたころは、CPUのコードを“芸術的”なまでに洗練させ、1サイクルでも削るのが最適化のすべてだった。サッカーなどに例えると、スター選手たるCPUが華麗なテクニックで単身得点を稼ぐイメージ。ほかにはせいぜい、特定の局面でDMAがアシストする程度だ。

ところが今日のアークテクチャーでは、パイプラインはもちろんキャッシュ・ストアバッファー・TCMといった布陣を最大限活用し、戦術的にパスを回さねば得点はおぼつかない。CPUは独りよがりのアクロバットなどせず、素直に振る舞うほうが成果が上がったりするのだ。しかしそれでも、アセンブリ言語の楽しさ、ハードウェアを直接駆動する感覚は変わらない。プログラミングそのものにワクワクするのは久しぶりだ。

#gemini