2013年5月3日金曜日

最適化 etc, リアルタイム作戦大失敗

とりあえず前回のエントリでリアルタイム処理は特殊だ、ということだけ解ってもらったと思います。
まぁこれは100メートルリレー400mとフルマラソンの違いであります。

リレーでは前の走者をトリガーに自分が全力で走ります。
「ここまで勝ってるからゆっくり走っていいよね」ではシバかれます。
お前は全力で100m走れ、最低でも11秒で走れ、速ければ速い程いい、 と言われるのがリアルタイム処理の世界だと思ってください。

実際に失敗例と成功例を見てみましょう。
便宜上失敗例といいますが、本当に大失敗したものは消えていますので同世代の比較ができません。ここでは「非常に優れているが、比較的の上では一歩及ばないもの」のことです。失敗例扱いされたといって気にしないでください。


Android のタッチパネル


僕は詳しくないのですが、どうも iPhone と比べて遅いらしいのです。
どのデバイスもハードウェアは常にポーリングを行っておりデータの入力があればメインのプロセッサに報せます。
このときに使われるのが割り込みというシステムです(敢えて割り込みを使わないということも考えられますが、ここでは割り込みとします)。
具体的には、ポーリングを行っているタッチパネルの制御用マイコンから、ホストのメイン CPU に信号がくるわけですね。
この信号を受けてホストのメイン CPU がデータの処理用のコードを呼び出すのですが

  • CPU の割当が遅い
  • いつタッチポイントのデータ転送を開始できるかわからない
  • そもそもタッチパネルの制御用マイコンというやつの素性がわからない
  • データの処理が遅い

などの理由で、データの処理開始が遅れます。
スタートが遅いとデータを取りこぼしたり、ユーザーへの反映までがずるずると遅れることになります。
データを取りこぼさないテクニックとしてはデータを取ったら後続のデータ処理をキックし、可及的速やかに処理を戻す、ということも考えられます。
割り込み処理中のイベントに対応するため、割り込み処理は可能な限り軽くしておくというのが基本なのですね。

ですがそもそも割り込みが入るのが遅い、となるとシステムレベルのプログラマにとっても厳しい事態となります。
iPhone の場合は、タッチパネルの制御マイコンによく知ったものを使っているので、 OS は最も最適な方法で一連の処理を終えることができます。
Android はベンダーの提供するドライバを呼び出すしかなく、全てをドライバの出来に左右されてしまいます。
ARM というプロセッサは割り込み処理がとても速くなるよう設計されているのですが、海のものとも山のものとも知れないドライバに処理を移すには、やっぱり最悪ケースを考えて移すよりありません。
不要なレジスタを退避し、いつ終わるとも知れないデータ転送を待つか、諦めてコンテキストを分離する……そういう世界です。

更に、タッチパネルというのは一見するよりも扱い難いものです。というのはマウスなどにはないノイズが多く、一回のスキャンで得られたポイント情報をアプリに全部渡すわけにはいきません。
システムはノイズを取り除き、有意な情報として処理したものをアプリケーションに渡します。この処理には、実際には数フレーム分の時間をとってしまうことがあります。


これら全て、ユーザーから見ればほんの一瞬の出来事ですが、下手をするとその一瞬は 0.1 秒くらいになってしまうかも知れません。或いは 0.03 秒で済むかも知れません。出来によります。
iPhone と Android では iPhone のほうがパネルの反応がよいとされるのは、ドライバやタッチパネルの制御用チップの出来の善し悪しに以外にもそうした事情があると思われます。
このことはユーザーにとっても、とても大きな問題です。詳しくは後述するような理由からです。


3d-coat 「書き味」の問題


3D-coat というとても素晴らしいソフトがあります。
これはスカルプチュアと呼ばれる 3D モデリングに使うソフトで、他に著名なものでは ZBrush や modo というのがありますね。
3d-coat もとても優れたソフトなのですが、フォーラムでは常に ZBrush と比較されてしまいます。
なぜか?
僕はアーティストでないのでそこまで拘りがないのですが、3D のアーティストの間では「書き味」というのがしばしば話題になり、 3d-coat の書き味は ZBrush には及ばないというのですね。
これはアーティストがペンタブで描いたものが、どれくらいよくポリゴン上に反映されるかというものです。
3D-coat はマルチレベルのボクセルを使用しており、これはとても高度で造形しやすいのですが、計算は重くなります。
アプリケーションはタブレットの入力を受け、2Dがボクセルモデルのどこに対応するかを計算し、ブラシを適用します。ブラシの適用は一回ではなく、内部的に何度も適用しますので、単位時間あたりの処理回数が多ければ多いほどアーティストのニュアンスを適用しやすくなります。
従って、重たいボクセルモデルを使用しているときと軽いボクセルモデルのときとでは、同じようにペンを走らせても結果が全く異なるのです。
重い場合はまず描いたものが飛び飛びに反映されてしまいますし、同じだけペイントしたつもりでも適応量がささやか過ぎたりします。ですからユーザーは(画面に反映したかどうか確認しつつ)ゆっくりとペンを動かさねばなりません。
ユーザーへの反映——これをフィードバックと呼びますが——フィードバックが細かく、速く、正確であるほど人間の動きも正確になります。ですので、フィードバックが遅いということはアーティストにとって手足を繋がれているのも同然だという意味です。

アーティストのいう「書き味」を科学的に考察すると、それはフィードバックの速度と精度のことであります。

enchantMOON が拘っているという「書き味」もまったく同様に、フィードバックの速度と精度だと思われます。


Android 4.1 以前 のサウンドシステム


Android は linux をベースとしています。
これはリアルタイムシステムではありませんので比較的時間にルーズな OS であります。
CPU の計算資源が足りない時にルーズになるのは仕方がないとしても、日頃から結構ルーズであります。
たとえば linux のサウンド出力のシステムは /dev にマップされた抽象化デバイスとして実現されており、プログラムはいくつものバッファを経て音を再生します。
バッファを経るということは、たとえば CPU の計算資源が割当られず、サウンドデータを出力できない期間があったとしても、割当てられたタイミングで沢山データをバッファを供給しておけば音が途切れずに再生できるというメリットがあります。
その一方、出力しようとした音が実際に出力されるのがいつになるかはっきりと解らないというデメリットがあります。
一般には、 1000ms 分のバッファがあったとしたら音が出力されるのは 1000ms 後です。遅過ぎます。
まぁ 1000ms というのは極端で、実際は 100ms くらいでしょうが、効果音がズレるなどという事態はゲームにとっては非常に致命的です。
その代わり1秒くらい CPU の時間が回ってこなくても大丈夫ですし、レート変換などもシステム側で行うことができるので、アプリは音を出すのがずっと簡単になります。もしレート変換を自分で行うとしたら結構大変です。変換するレートの最小公倍数ぶんの時間がかかると思ってください。

一方、 iPhone は MacOS の CoreAudio という薄いレイヤのアーキテクチャを利用しています。
これは OS がサウンドデバイスの管理をアプリに任せる反面、プログラムがデータを出力してから即座に音を出す事ができます。

さらに、前述したタッチパネルの遅延があることも思い出してください。
このため、 Android にはリアルタイム演奏のアプリケーションが少ないですし、奥深いアクションゲームの体験といった点においても遅れをとっているわけです。

チケットゲート


生憎、皆さんが比較体験できない例です。
もう大分前の話ですが、仕事でオンラインチケットを検査してゲートを開閉するシステムを作っていたことがあります。
正確には僕が作ったわけではなく、最初につくったシステムがあまりにも遅かったら、その高速化を頼まれたのです。
研究所からの要求はチケットの検査から結果が出るまで 120ms  というものでした。それが実際は 2000ms 以上かかっていたのです。
仮に、駅の自動改札に Suica をタッチして2秒待たされたと考えてください。
事故になりますよね。
ETC とか考えただけで憂鬱になります。
僕のはそこまでシビアなシステムではありませんでしたが、最終的には目標の性能を達成できました。
Suica もえらい苦労したと聞きます。
自動改札を通るとき、たまには思い出してあげてください。ゲートを速やかに開けるために何ヶ月も休まないでプログラムを書いた人がいることを。

卑近な例としてチケットゲートを上げましたが、所謂組み込みでは大概リアルタイムです。
ロボットや車の操縦なんかですね。
人工衛星やロケットなど真空に近い状況下で動作するシステムは

USB

リアルタイムシステムというと組み込みプログラミングという印象強いでしょうが、デバイスドライバだって優れた応答性が要求されます。
USB デバイスは言わずと知れた拡張端子の傑作であります。
USB 以前のことを考えてください。RS-232C, パラレル、 SCSI のアンフェノールピン……。
USB コントローラチップはパワフルですが、反面システムに対する要求も高いものでした(OHCI と UHCI で仕様が分かれたのもまずかったけど)。

まず 32bit の PCI アドレス空間が必要です。これで Win3.1/DOS は対応できなくなりました。さようなら。 Windows95 ですら OSR2.1 以降のスペシャルバージョンが必要でした。
PCI アドレス空間の問題を解決したあと、待っていたのは CPU 計算資源の問題でした。
USB には割り込み転送モードやアイソクラナス転送モード、バルク転送モードがあります。
バルク転送モードは CPU を消費せずに大容量データを転送できるモードですが、それ以外では USB コントーラーの割り込みに対して CPU が高速に反応しなければ、HUD ではマウスカーソルが飛びますし Web カメラの映像は途切れてしまいます。
高速な CPU と優れたOS/ドライバがなくては USB のように CPU 食いのバスを快適に利用することはできません。USB3.0 が失敗例にならないことを祈っております。


焼きガニ、TCP/IP, Ethernet, ブロードバンドネットワーク


TCP/IP や Ethernet が失敗?いえいえそんなことは決してありません。
ただ TCP/IP がそれまでのネットワークプロトコルのなかで最も CPU 資源を食うということを知って欲しかっただけです。
TCP/IP は沢山のポートやソケットを使って並列的に通信を行い、アプリケーションの実装とも分離されているのでそれまでの PC のシングルタスク OS では実装が困難でした。
タイマ割り込みという方法で、擬似的に TSS (時分割スケジューリング)を行ってCPU資源を分散する実装もありました。(Waterloo TCP など)

そして Ethernet カードはコモディティ化に伴い、高価なハードウェアのバッファをケチるようになりました。
Ethernet カードに内蔵するバッファが小さいということはブロードバンドネットワークで大容量のデータを転送する際に頻繁に CPU に割り込む必要があるということです。
「バッファが一杯だから速くデータを拾ってくれ!」と CPU に言うわけですね。CPU が割り込みを受けてデータを拾ってくれるまで、 Ethernet は次のデータグラムを受信できません。
USB のときと同様、帯域を使いこなすにはリアルタイム性の高い処理をこなす必要があるのです。
かといってあまりにケチると Realtek のチップのように燃えてしまうこともあります。俗にいう焼きガニというやつです。

不出来なチップとドライバの組み合わせでは、一度に大容量データを転送するとシステムが死んでしまうこともありました。
当時の遅い CPU と OS の組み合わせでは、 100Base の LAN の帯域を使えないことがありました。
現在でも、不出来なルーターを使うとルータのCPUの処理が追いつかず、ネットワークのスループットを下げることがあります。それは仕方がないことで 1Gbps もの帯域を非力な組み込みプロセッサで処理し、ルーターやファイヤーウォールを実現するのは簡単なことではないのです。

ゲーム機


ゲーム!
まさにゲームというのセンシティブな入力のフィードバックのスピードが要求されるシステムの最たるものです。

ファミコンのコントローラが優れていたのは、ただのスイッチではなくマイコンを内蔵していたことでした。
いちいち本体まで信号を送らずとも、その場で処理していたからこそ細かい入力のニュアンスを伝えるだけのクロックを実現できたのです。
もしコントローラがスイッチを並べただけのデバイスだったら、本体までの長いケーブルを考えると高いクロックを供給できませんし、遅延や電圧降下の問題はプレイヤーに及んでいたことでしょう。
そうでなければカセットビジョンのように本体とコントローラをまとめるしかないのです。

最近ですと、例えば PS3 版と Xbox360 版のスト4とでは遅延が PS3 で大きいということは問題視されました。
さらに同じ PS3 であっても、発売直後と現在のハードウェアとではゲーム中に PS ボタンを押したときの快適さが大分異なります。
ゲームプラットフォームの世界ですと、ほんの1ms以下の違いがフレームをまたぐかまたがないかというところで体感可能な差に見えてしまうものなのです。
Rocksmith という実物ギターで遊ぶ音ゲーは、信号レベルの遅延を嫌って音声をアナログ出力することを推奨しています。

……というのはソフトウェアの話ばかりでなくハードウェアレベルでの応答性の話になってしまいましたが。
Xbox360 では kinnect といった入力システムを備え、ユーザーの体全体の動きをゲームに反映させることに成功しています。
PS2 では Eyetoy, PS3 では PSEye/Move といった入力システムもあります。
こうした入力デバイスはフィードバックの時間差の問題が関わりますから、 システムの消費する CPU 時間が大きくなってしまいがちです。そのためゲーム開発者からは支持を得難いという側面があります。
フィードバックはゲーミングの命であるからです。

かつて Windows CE を採用してしまった失敗ハードウェアというのが少数ながら存在します。 Gizmond, Dreamcast などです。
Pipin@ やマーティがどうだったかは知りませんが、池袋GIGOで料理の注文受付システムとして余生を過ごす Pipin@ を見た事があるので、きっと似たようなものだったのでしょう。
これまで見て来たように、リアルタイムシステムに求められるものは高速な割り込みアーキテクチャと、TCP/IP や USB のように CPU を浪費するコンポーネントが身の丈にあっていること(=コストがコントローラブルであること)です。
先進的な(CPUを食う)デバイスと、高速な処理は互いに矛盾する要求に見えますが——何も不可能を可能にしなければいけないわけではありません。
リアルタイムシステムは、アプリケーションに対して「〜この処理をするのに最悪xx秒かかるよ」ということを保証すればいいのです。
アプリケーションはそういうものだと思ってシステムを使用すればいいのですから。

そうしたものをざっくりオーバーヘッドと呼んでしまいましょう。
システムを利用するのにアプリケーションが犠牲にする実行時間のコストのことです。
WindowsCE を利用したゲーム機はこの高価なオーバーヘッドを支払う必要がありました。早い話が、遅い OS だったわけです。
幸い、 Dreamcast  は WindowsCE を使うか使わないかをゲームが選べました。なので本当に失敗したゲームはほんの僅かです。使ってしまったゲームはフレームレートや描画オブジェクトの数などに多大なペナルティを受けました。

誤解なきよう、 Linux や BSD, Windows など現存する OS の出来が悪いのではありません。
そうした OS は不出来なアプリケーションから他のプロセスやシステム自身を守るため特権モードを備え、アプリケーションが動作するユーザーモードとは異なる空間を保護しています。
ですので特権モードにスイッチするシステムコールで、オーバーヘッドが大きいのはどれも共通です。
重要なのはユーザー(アプリケーション)の要求に合っているかどうか、だけなのです。
だって今時、インターネットやLAN/Wi-Fi, Bluetooth, USB などはどのゲーム機でも対応必須じゃないですか。機能を削れないのなら仕方がないことです。

従ってゲーム機では時として、アプリケーションは OS の機能を OS を使わずに実現する、というようなことすら行います。
ゲームに限ったことではありませんけれど、ゲームでは特に重要度の高い OS の代替技術があるものです。例えばスレッドコンテキストスイッチングを減らす fiber, メモリの割当を自前で行う dlmalloc などはとても人気がありますね。

iPhone

最後に、失敗例ではありませんが、 iPhone についても言及しましょう。
iPhone はとてもゲーム機に似たシステムです。
個人的には iPhone のプログラミングなど真面目にやったことはないのですが、 WWDC で聞いた内容からして、ゲーム機そっくりのアーキテクチャです。少なくとも Android とは基本的な発想からして異なっています。
iPhone のスレッドスケジューラは Mach と同様 PC ライクでリアルタイム的ではないのですが、それ以外はとてもゲーム機に似ていると言えるでしょう。

iPhone3G 当時、システムのオーバーヘッドを軽くするためにマルチタスキングを廃し、60Hz のメインループを共有するアーキテクチャでした。
Jobs CEO は基調演説で、マルチタスクを行うとこうなってしまう、と WindowsCE の画面を見せました。
身の丈に合わないシステムは外すという思い切りはそんじょそこらのアーキテクトには到底真似できない決定でした。
iPhone はハードウェアを一本化して VM なしのネイティブコードですし、 Android とは正反対のアーキテクチャであります。
CoreAudio, CoreGraphics などの薄いレイヤを備えた応答性の高いシステム、 Cocoa はバグだらけの実装でしたが、まぁそれは未だに Android だってバグだらけです。

ともあれ、その実直なアーキテクチャが指に吸い付くような素敵なホーム画面(アイコン一覧)を提供してくれていたのです。
もっとも、 iPhone3G は CPU が遅い上、ネットワークもゴミ以下ときていたのでアイコンの一覧を華麗にスワイプする以外では何の意味もありませんでしたが……。
今僕が特に不満もなく Android を使えているのはあの頃の iPhone を触っていたからだと思います。
素晴らしいメニュー画面と劇遅のブラウザ、一週間遅れて届くメール、認証中に画面がブラックアウトする IMAP4……それらの対比が、いい感じの中庸を齎してくれた Android 端末の登場によって媒介されているのです。

ですがよい話ばかりではありません。
iPhone3G が日本で発売された直後、そのあまりにも遅い OSK (On Screen Keyboard) は問題になりました。
何やら色々あったと聞きますが——最終的に、ある方が泥を被って寝ないで修正したのだそうです。
悪い事に、 OSK が遅いとフィードバックが得られずに誤入力が増えるばかりか、他のアプリケーションをもブロッキングしてしまいます。
これは致命的でした。
それでも今尚 1GHz の snapdragon の Android と iPhone3G の改善版の OSK とを比べると(漢字変換精度はともかく)入力しやすさという点では圧倒的に iPhone のものが優れているとさえ思えます。

当時の iPhone3G を使っていた人なら、リアルタイムシステムの難しさというのを身に染みて体感できたと思います。

0 件のコメント:

コメントを投稿