2013年5月30日木曜日

Wine 64bit 版は mac で動かないのか

Wine 1.5 系の開発が進んでいます。
僕はそんなに wine 詳しくないんですけど、そういえば 64bit 版って mac 向けにビルドできるのかな?と思い立ちやってみました。

結論からいうと結構あちこち直す必要があります。
大変です。カジュアルにはとてもいきません。

まずもって Xcode に付属の gcc-4.2 では wine のビルドがとおりません。
どうも可変長引数のスタックレイアウトが Win64 と互換性がないみたいで、これをサポートしてくれる gcc-4.4.4 が必要です。 mac ports で入れる分には簡単でいいですが。

そのあとはインラインasmでやたらエラーが出るのでこれを直して回ります。
movq で GP レジスタから XMM レジスタへロードするところが軒並み通らないので、メモリからのロードに変更しました。
GP レジスタへのロードはそのまま残さねばなりません。ABI の都合上、必要そうだからです。
インラインアセンブリはどこでエラーになったのかパッと見解り難いのが難ですよねぇ。
temp が残っててくれればいいのですけども。

あとは signal ハンドラのレジスタロードです。
mac 版は 32bit レジスタ用の実装しかないので、 64bit レジスタを取るように修正して回ります。

これで大体通るのですが、大きな悩みどころが残ってしまいました。
スレッドエントリの初期化で fs.base を退避/取得するところがあります。
BSD/Linux は syscall やコールゲートを使ってユーザーランドから fs.base にアクセスできるのですが、mac にはこの syscall がありません。
fs.base には rdmsr/wrmsr 命令を使ってアクセスする必要がありますが、これは特権命令なので特権モードである必要があります。

まぁ、無理に特権レジスタにアクセスすることもないか……。
ネイティブスレッドの TLS にでも入れときゃいいじゃん、ということでそういう風に書き換えたのですが、 Wow 側が fs.base に特定の値を期待してたらまずいことになります。
Win32 の頃はカーネルが FS にスレッドコンテキストのディスクリプタを入れていることも期待しており、これなら i386_set_ldt API を使って mac でも実装が可能です。
でもまぁ、プロセッサが命令レベルで特権命令だっていってるんだから、 Wow でもユーザープログラムが勝手に何かを期待することもないだろう……と祈ることにします。

(ちなみに、ユーザーランドから fs.base をリードできる新しい命令があるのですが、まだ Sandybridge では実装されてないようです。それにしても AVX はあるけど積和演算(FMA3)は実装されてないとか、色々新しい命令に取り残されている気がする。案外使えないな Sandybridge)

というわけで色々禍根を残したのですが、問題はそれだけに留まらない。
危なそうな警告を眺めていると int とポインタのキャストが結構たくさんある。
Wine の各種 driver (デバイスドライバじゃなくてプラットフォームごとのモジュールを driver と呼んでるみたい)は、例えばクリップボードの列挙を行うときに 32bit 版であれば、 Mac のネイティブ API が返すポインタを識別子としてそのまま int へキャストして返しています。

Win64 API でもそうした列挙に使う識別子はぜんぶ int のまま。一方で Mac のプロセス(Mac に限らず大抵の OS はそうだと思うが)プロセスイメージの仮想アドレスマッピングを 32bit 以上のアドレスで使います。
 仮想アドレスの割当からコントロールして 32bit に収まるアドレス空間を使うことも不可能ではないでしょうが、 OS レベルのメモリ割当を細かく使うと非常に高くつくし手間がかかる。
かといってキャストでは死んでしまいます。
下位ビット払って上位は固定……なんてのじゃダメかな。 Mac の API がどんなアドレス返すのか期待するのはかなり無理があるかな。

なのであちこちに map を作って、自分で払い出した識別子とアドレスを変換するようにしなければならない……ですね。
ああ、面倒くさい。
仕事ならやるよやりますよ。
でもこんな時間に疲れて帰って来て、寝る前にやることとしてはちょっと気が重い。

というわけで時間あるときにまたリトライしようと思います。

2013年5月26日日曜日

勇者のくせにこなまいきだ

あの「勇者のくせになまいきだ」の新作がPSMで発売です。
——え? PSM って何だって?
えーと、それを説明するとさすがに長くなるので後回しにします。
PSM は PlayStationMobile の略で、ゲームの造り方の部分——技術的な棲み分けの話であり、とりあえず今は VITA や Xperia なんかの一部スマホで動くゲームだと思っていてください。
遊ぶぶんには他のゲームと同様に PS Store で買って同じように遊べます。
5/28まで300円です。その後は……幾らだっけ? 500円かな?

勇者のくせになまいきだ——略して「ゆうなま」ではプレイヤーは破壊神という名の、一本のツルハシとなって地面を掘ってダンジョンを作ってきました。
掘ることで地中の生命を活性化し、養分の循環と集積をコントロールし、より高度なモンスターを生み出して勇者を撃退するわけですね。


新作こなまいきはなぜかアクションパズルゲームです。
同じ色のモンスターを三つ揃えると生み出し、勇者の元に送り込んで戦わせることができます。

勇者がやってきました。
——テイストは安定の高度なパロディと内輪ネタです。心配になりますね。
勇者はひとりでは来ません。必ず他にたくさんのお供を連れてきます。

これが勇者を迎え撃つ画面。
一目見て、 HEXIC や Gems, パズルクエストシリーズのようなタイプとわかりますね。勇者のセリフはニコニコ動画風のコメントで流れますが意味はありません。
ブロックは封入されたモンスターごとに色分けされています。
上のスクリーンショットでいうと、緑→青→赤の順に強いです。 この例では赤が最強ですね。茶色い、何もモンスターがいない無色のブロックもあります。
これら以外に、水色と紫のドラゴンブロック、そしてお邪魔ブロックがあります。
画面をタップするとタップしたブロックが消えます。(お邪魔ブロックはタップで消せません)
大抵の場合、タップして消すブロックは犠牲です。無駄に消えてしまいます。そのかわりスペースが空くので、上から他のブロックが落ちてきます。
落ちる前に左右のブロックを一個ずつスライドさせて滑り込ませることも可能です。
タップ & スライド。そうして同じブロックを三つ揃えましょう。

縦か横に三つ揃うとブロックは消え、中のモンスターが画面左側の勇者のところに送り込まれます。
揃えて意味があるのはモンスターが入っている色付きブロックだけですよ?


三つ揃えるとモンスターを送り込めるだけでなく、三つのうちの最後に動いたもの一つが一段高度なモンスターに進化します。
水色のゴーレム、紫のドラゴンのブロックは赤を揃えて進化させる必要があります。

今まさにドラゴン三匹が勇者と戦っています。
そこにピクシー(青)を送り込んだところです。

写真では五匹のピクシーが出動しています。
これは5コンボ目だからです。コンボを続ければ続けるほど同じ三つのブロックから大量のモンスターを送り込むことができます。
普通のパズルゲーム同様、コンボはとても大事です。

コンボは連鎖とは違います。消えるアクションと次に揃うものが連続している必要がないのです。
消えるアクションが続いている間も他のブロックを操作できますので、揃えられる組み合わせが思いつく間は揃えまくれます。

そしてスクリーンショットの上の方を見てください。白く発光している赤ブロックがあります。
これは養分の詰まったブロックで、普通の赤と同じに扱えますが、タップして消すと養分を解放し、周囲のブロックをアップグレードしたりお邪魔ブロックをただの無色に還元できます。
効果範囲は養分の量によります。一番弱くて上下、次が上下左右、次が隣接全部です。
養分を解放させるには必ずタップして消さなければなりません
これを活用すれば、普通に頑張るよりも簡単にドラゴンを送り込めるようになりますよ。

勇者もやられっぱなしではありません。
上のスクリーンショットの左側、勇者の上を見てください。
この勇者は瀕死ですが、こちらがあと4回掘ると攻撃してきます。
勇者の攻撃とは、こちらのブロックをお邪魔ブロックに変えてしまうことです。
攻撃パターンは勇者によって違います。 ですが、お邪魔ブロックに実際に変わる前にどれとどれが変わるか予告がありますので、赤い予告マークが出たらそこからはよく考えてください。
ドラゴンやゴーレムを守り、反撃の体制を整えましょう。

時間切れになる前に勇者を倒せました。
画面一番の右のメーターが勇者の効率進捗で、一番上からスタートして一番下に魔王がいます。
魔王とは戦わずに生け捕りされますので、一番下まで来られたらゲームオーバーです。
勇者を一人倒したら次の勇者はまた一番上からスタートです。
全ての勇者を倒してステージクリアを目指しましょう。

モンスターの強さと色の関係はおわかりですか? 弱い方から
緑→青→赤→水色→紫
です。
お邪魔ブロックはタップしても消えませんが、他のブロックが消えるときに巻き込んで消すことはできます。
ただぷよぷよのお邪魔ぷよとは違い、隣にあれば消えるというものじゃありません。
揃えたときに隣接するお邪魔ブロックのうち、揃えた方向にあるものに限られます。縦に揃えたときは隣接する縦、横に揃えたときは隣接する横、という風に消えます。
少々厄介ですね。
そのかわり、お邪魔ブロックがいくつあろうとも、横なら横方向のもが全て消えてくれます。(お邪魔ブロックが続いている場合に限る。真っ直ぐ横方向の途中にお邪魔ブロック以外のブロックがあると消えるのはそこまで)

それから、ドラゴンを送り込むときはすべてのお邪魔ブロックが消えます。

まぁ、細かいルールについても触れましたが、やれば解ると思います。チュートリアルもありますし。
チュートリアルは操作方法のみならず、ヒントを教えてもらえます
そのヒントがなければ、ステージ6以上のクリアは難しいでしょう。

このゲーム、結構難しいです。
歯を食いしばって、息を止め、呪詛の言葉を吐きながらひたすら揃えまくりましょう。
強い勇者を倒すには優れた防御と形勢の反転、そして雨のような攻撃が必要です。
もちろん強いモンスターを作り出すことも重要なのですが——それだけに集中してはいけません。

実のところ、これはパズルゲームではありません。
アクションゲームです
頭を使えば、打開できる局面もあろうというものですが基本的には指先を動かしましょう。

あっ、そうそう。
買うときの注意ですが、 PlayStation Store は Vita 用ゲーム, Video, PlayStation Mobile  と三つのタブがあります。
PSM のゲームはその一番右のタブですんで、そこだけご注意。





2013年5月25日土曜日

Wolfram Alpha で演算するメモ

ビット演算

 普段は google 電卓で間に合わせてしまうことも多いのですけど、 ビット論理演算は苦手な部分です。
さらに四則演算においても 64bit 整数を扱っているとどうにも使い難いことが結構あります。なぜか実数扱いになってしまって in hex で16進表示にできないようなパターンですね。

そういうときは Wolfram Alpha を使うことにしています。
しかしこれも、技術専用の計算機ではなくて幅が広いものなので、プログラミング気分で式を書くとエラーになったり assume されたりすることもしばしばあります。

例えば 0x プレフィックスの16進記法は文脈によって解釈されません。 '|' や '&' といった論理演算子は使えますが、 xor がありません。
そういうときは関数フォームを使いましょう。
関数フォームといっても bitxor() ではありません。 bitxor[] です。
bitxor[ bitor[0xdead000000000000, 0x0c00000000000000, 0x00a0000000000000], 0x5555000000000000]
結果は以下のようになります。

http://www.wolframalpha.com/share/clip?f=d41d8cd98f00b204e9800998ecf8427ea3esf8qete

まぁ、少々煩わしい。
通常、 wolfram alpha は、
to base16
で HEX 変換してくれますが、入力が既に HEX で計算している場合に to base16 を付けてしまうとなぜか Result が表示されなくなり other base conversions にも base16 の結果だけが抜けてしまうのでめんどいです。
たぶんバグだと思うのですが……。ずっとこうですから……諦めてます。

この場合は、 to base16 を付けずに other base conversions の base16 結果を使うか、その下の other data types のところの more と big endian ボタンを押して 64bit integer 且つ Big Endian 表示の結果を手に入れましょう。
なぜデフォルトが Little なのか理解に苦しみますが、これももうずっとこうなので……。
more を押さないと 64bit integer や double precision floating point の結果が見られないのも、もう 21 世紀なんだから一つ頼むよ!と思うところです。

行列計算

あれば octave、 3D に特化した計算なら blender コンソールを使うのですけどちょっと今持ち合わせが……なときとかに重宝します。
特に Blender コンソールは python 使える人には絶賛お勧めですよ。クォータニオンも使えるし。でも Matrix3, Matrix4 といったクラスベースなので、変態的な行列を使うときは逆にちょっと厳しい。

wolfram alpha での行列記法は, 2x2 の単位行列が
{{1, 0}, {0, 1}}
です。 octave と大体一緒ですかね。むしろ却って馴染み易いかも。

行ベクトルが {0, 0} で列ベクトルが {{0, 0}} です。
注意するべきは、行列の転置は
transpose M
または M^T です。 M' でもなければ M^t でもありません。

簡単な例で言うと、2Dベクトル(0, 0)の平行移動(-1, -1)は
{{1 , 0, -1}, {0, 1, -1}, {0, 0, 1}} * {0, 0, 1}
宗教上の理由で列ベクトルが使いたい人は
{{0, 0, 1}} * {{1 , 0, -1}, {0, 1, -1}, {0, 0, 1}}^T
です。DirectX 脳の人は注意してください。
例えば以下。 上向き単位ベクトルを 30deg 回転させるには
{{sqrt(3)/2 , -1/2}, {1/2, sqrt(3)/2}} * {0, 1}
または
{{0, 1}} * {{sqrt(3)/2 , -1/2}, {1/2, sqrt(3)/2}}^T

結果は,
http://www.wolframalpha.com/share/clip?f=d41d8cd98f00b204e9800998ecf8427ecq2au706qv



2013年5月22日水曜日

Flickr が変わった

Xbox One が発表されましたが個人的に一番激震が走ったのは Yahoo! による tumblr 買収……でもなくて、 flickr の大幅なアップデート。

Photostream の大幅なレイアウト変更もありますが、メンバーシップに大鉈が振るわれました。
一見して Pro アカウントの消滅と言いますか。 Free アカウントの底上げと言いますか。ビジネスモデルに大きな修正が加えられたようです。

背景には年間 25$ ほど取る Pro アカウントでは利益が伸びなかったのか、僕も身に覚えがありますが、年間を通して大量アップロードしているわけじゃないユーザーは Pro アカウントが失効しても「まぁいいや」と次に沢山写真撮るまでは放置しちゃうわけです。
要するに自動更新してない Pro アカウントですね。

本日(日本時間では昨日)より Free アカウントのユーザーは以下のようになります。
原文はこちら

  • 1TB ストレージ
  • オリジナルクオリティの維持
  • 広告表示
  • 一つの写真は最大200MBまで
  • 一本あたり 1GB までの HD ビデオのアップロード
  • 3分間のビデオ視聴(なんだそりゃ)

重要そうな順で書いてます。
オリジナルクオリティの維持というのは、 flickr のユーザー以外には想像しにくいかも知れませんが、とても重要な要素です。
twitpic とかに写真上げると、 JPEG の圧縮率が最高になるよう再エンコードされます。 従ってどんな写真でもがっかりするような品質の低下を確認できます。それも誰にでも解るような露骨な劣化が。
flickr はそういうことが起きないサービスの一つなのですね。

さらに、お金を払うと以下のようになります。

  • $50/year で広告なし
  • $500/year で 2TB へ拡張(最大)

No Ads 且つ 2TB (Doubler) にするには合計 $550 払わないとイカンのでしょうか。
たぶんそうだと思います。 Doubler が No Ads の上位概念とは読めないので。

ちなみに拡張の上限は 2TB で、それ以上には増やせません。アカウントを増やすしかないようです。
さすがに 1TB だって充分すぎるでしょうが。

いやー、それにしても、サービスの提供側がこんなに厭そうな顔してるのが目に浮かぶ Lean more も珍しいですよね。
やりたくないならやめちゃえばいいのに
誰が使うんですか、 Doubler とか…。

メールには"ストレージが 1TB になったよ!"くらいしか書いてなかったので喜んで観に行った人は大概混乱しているようですね。

特に今 Pro アカウントの人は特に混乱しています。
だって今朝の時点では Pro アカウントがなくなってしまうように言われていましたから。

まず、 Pro アカウントとは何だったのか、それを確認しましょう。(原文
  • 無制限 写真アップロード(1枚あたり50MBまで)
  • 無制限 ビデオアップロード (90秒まで。一本あたり最大500MB)
  • HD ビデオの視聴
  • 無制限のストレージ
  • 無制限の帯域
  • オリジナルクオリティでの記録
  • 写真の置き換え
  • 60までの Group/Pool への投稿
  • 写真の閲覧数、統計へのアクセス
  • 他人への公開解像度設定
  • 広告なしの閲覧、共有
こうしてみると地味に色んなご利益あったんですね。

さて、この便利な Pro アカウントにアップグレードしたいと思ってももうできません。それは事実です。
今 Pro の人は今後どうなるのか、それが問題です。
原文

  • "自動更新の" Pro アカウントは今のところ更新可能
  • Pro メンバーの資格者は 2013/8/20 まで Free アカウントに切り替えるか選べる.
  • "Pro Gift" はもうあげられません
  • "Pro バッチ"ももう付きません
”自動更新”(Recurring) の定義を巡っては案の定、Help フォーラムでも揉めていました。
数少ないながら、 flickr スタッフからの明確なメッセージもポストされていますので、僕の考えを述べるよりもそれを読んだほうがいいと思います。

フォーラムのスレッド:
 http://www.flickr.com/help/forum/en-us/72157633549071436/

スタッフのコメント:
 http://www.flickr.com/help/forum/en-us/72157633549071436/#reply72157633533344529

僕のように自動更新にしていない Pro アカウントは失効後、更新できなくなります。
ですが、 flickr のチームは自動更新契約するためのフォームを準備中のようです。

(そのフォームができる前に失効してしまう人は困るでしょうね。expire する前に更新しとけばいいのでしょうが)

スタッフのコメント2:
http://www.flickr.com/help/forum/en-us/72157633549071436/#reply72157633533510777

おそらく今のところは(たぶん 8/20 までは) Pro をやめて Free になる決定を明示的にしない限り、 Pro アカウントには何も起きないということでしょう。


flickr は他にもいろいろと変わりました。
アップローダも新しくなりましたし、 Photostream の上部にあるカバーイメージはスクロールすると昔のゲームのレイヤーみたいでちょっとかっこいい。
Help フォーラムのリンクは画面下部にあるのに、タイムライン表示の状態で一番下までスクロールするのが事実上不可能だとか、そういう腐ってる部分もありますが。

まぁ、ちょっと心配しましたが、僕の flickr アプリ "Heurickr" は問題なく動いています。
確かに Pro バッジは Contacts リストに表示されなくなっているけど、それ以外は何も変わりありません。

2013年5月20日月曜日

GITADORA アプリの新しいライブシステム

今更ですが——GITADORA アプリのライブシステムが新しくなってひと月ほど経ったでしょうか。

私は全然新曲がアンロックできなくなりました。
ゲーセンで他の人を見ると結構新曲遊んでる人がいるので、ああ、みんな頑張ってるんだなと思うのですが、新しいライブシステムはかなり面倒くさい。
今までみたいに得意なやつだけチンタラ遊んでいてライブ進捗を稼ぐことができなくなりました。

新しいシステムは——
  • JOIN から 24 時間とかなり短縮された
  • 全員が同じタイミングや進捗を共有してないのはこれまで通り
    • 僕が 30% しかなくて焦っているときでも 99% まで薦めていて余裕、という人もいる?
    • とはいえ進捗の片翼たるミニゲをクリアしたライブ内累計の pts は共有されている
  • キャパと観客数を稼いでゆく
  • 12000人程度で100%。つまりキャパと観客でトータルで 24000pt くらいは必要
  • エフェクトだけではパーセンテージを稼げないのでエフェクトが 1000pt 必要
  • だけどエフェクトを稼いで HEAT UP しないと何の意味もない
  • ライブ終了後、HEAT UP のパーセンテージのみが引き継がれる
  • プレイポイント 1000pt で 30 分間のボーナスタイム。更に 1000pt ごとに累積+延長

となっております。
ミニゲームは拡充されて、そこそこ面白いのもでてきました。

要するに流れとしては、キャパを稼ぐとキャパの pts までは観客を増やせるようになります。
キャパが 500pts しかないのにそれ以上観客数を増やすゲームを遊んでも、おそらく意味はない……という風に取れます。(pts は増えていくので、後からキャパを増やしてもいいのかどうかちょっとはっきりしませんが)
これで一見してパーセンテージは増えていきますが、これは仮の進捗であり、お客を集めるだけでは意味がありません。
いい感じに増えたらエフェクトのポイントを突っ込んで HEAT UP させ、進捗を確定します
HEAT UP したぶんだけ進捗メーターが黄色くなっていきますので、これが本当の進捗です。
これを無数に繰り返して 100% を目指します。

ゲーセンでプレイして稼いだプレイポイントの使い道はどうなったのかというと、 30 分のボーナスタイムを発動させて、この間ミニゲの利得をブーストさせることができます。
一回 1000 pts 必要で、キャパと観客数に別々に適用します。
一回あたり、ブーストのレベルを +1 し、持続時間を追加から 30 分延長させます。
追加した瞬間から起算して、ですので二回連続で突っ込んでも一時間にはなりません。
28分経過後くらいを狙って追加のポイントを突っ込みましょう。
運が良ければ誰かが気を効かせて突っ込んでくれるかもですよ。

ブーストをなるべく高めてミニゲームを遊ぶのが重要といえます。
でもまぁ、みんなそれぞれ事情がありますので、そんなタイミングあわせられないですよね……。

ちなみにエフェクトですが、これはプレイポイントから変換できてしまいます。
ですのでエフェクトのミニゲを遊ぶ機会はもうないかも。ゲーセン行けないときは重要ですが。
僕はエフェクトのゲームが平均 90pts を叩き出せるので気に入ってたんですけどもねー。

ポイントの平均が 80 pts 、1ゲームの平均コストが 6 としてざっくり計算すると12時間あたり一人 2400pts を稼げます。
(2時間で体力は全快するので、 12 時間あたり 6 回は全力でミニゲームができるものとします)
ですので一回のライブで 100% にしようとすると、だいたい 10.4 人のメンバーがかなり真面目且つ何かを犠牲にしてミニゲームをやってくれないといけないわけですね。

ゲーセンでプレイしたプレイポイントがあれば条件はかなり改善します。
ブースト Lv30 くらいまで突っ込んでおけばミニゲームのポイントは +50% くらいになった気がするので。延長を続ければ更にレベルが上がります。12 時間連続で延長した場合、 Lv54 まで到達しますので、だいたい 7-8人くらいムキになってやってくれれば一発クリアも不可能ではないとは言えるでしょうか。

まぁ、お察しのように一回では厳しいものがあります。
だいたい、30分おきにブーストを続けるとか、 2 時間ごとにミニゲやるとか、あんまり現実的じゃないですか。
大抵は途中で忘れて誰も HEAT UP しないまま稼いだ観客を無駄にしてライブ終了したりします。
それどころか、せっかくのプレイポイントも持ち腐れのまま終了なんてことも珍しくないですし、 Lv 30 までブーストしてプレイポイントを稼ぎながら延長とかやってたら忙しいですよ。ミニゲームやってる暇ないまま延長忘れて水泡に帰して心が折れたこともあります。

おっとバンドメンバーを恨まないであげてください。
メンバー同士、数時間のラグがあるのです。自分からするとライブ終了ギリギリで、今すぐ HEAT UP させなきゃまずいという状況でも、他のメンバーからするとそうでもなかったりするんです。

まー……。
気長に。
気長に……。

2013年5月17日金曜日

DxO サポート情報

遂にツァイスレンズサポート! DxO が E マウント用のツァイスレンズ 28mm/f1.8 ZA 用の光学モジュールをリリース予定に入れました。 2013/5 となっているので近日中にリリースされると思います。 NEX シリーズ全てをサポートするようです。 DxO なくても不満の出るようなレンズじゃありませんが……どうなるか楽しみでもあります。 新しい E マウント 35mm f1.8 の光学モジュールも 2013/6 に予定されているようです。

2013年5月15日水曜日

変態仮面実写版

アイアンマン

先月末のことですが、観て来ました、変態仮面。劇場版。実写ですよ。
写真は残念ながら、バルト9にあったアイアンマンです。誠に遺憾ながら、変態仮面のはありませんでした。

いやー、面白かった

正直あんま期待してなかったのは、邦画特有のタレント中心の組み立てがあるんじゃねーかなって気がしていたもので。
誰得のお涙頂戴もあるんだろーなと思って身構えてたし。
しかも原作って短いんですよね、リアルタイムで読んでた記憶もないし……。

いやそんなものは杞憂でした。杞憂。

なんでこんな小刻みにバカな笑いを突っ込めるの!?ってくらい盛り沢山で、くだらない下ネタ一本槍じゃなくてかなり高度な下ネタは是非必見です。
あんまり面白そうじゃないなと思ってるあなた! あなたは変態仮面でなく、むしろ敵に注目していただきたい。

変態仮面って敵らしい敵がいたのか!? とお思いの方もいらっしゃるでしょう。
ええまぁ、少なくとも僕はそんな認識でしたよ。流しの強盗とかと戦ってるのかと。

敵はねー、バカですよー。
変態仮面の通う学校の地下に眠る埋蔵金を狙ってやってくるんですが、これが、すごいバカですよ。
最大の見所だと思います。

「あー、これはアイアンマンのあれだな」とか。
「ココは普通の映画だったらカーアクションだよな」とか。
「これはまんまあれだな」とか。
パロディもオマージュもありました。 アイアンマン
見終わった後の喫煙所は既にハリウッドリメイクの噂でもちきりでしたね。
もちろん噂です。というか「だったらいいな」というお話。(上の写真はイメージです)

ほら、日本生まれのコンテンツってハリウッドで映画化されてライフサイクル終了ってイメージあるじゃないですか。

何にせよ劇場であれだけ笑えるというのもなかなかない話。
ぜひ劇場へ足を運んでくださいませ。
個人的には、前回「貞子3D」で堪えきれず噴き出してしまい——いや、僕に良識がないわけじゃないですよ。隣のおっさんなんかは周囲の目も気にせず爆笑してたし——まぁ笑っちゃイカンと思いながらポップコーン噴いちゃうのは変な汗かくでしょう。

レイトショー終了後、午前二時前に新宿に放り出されて途方に暮れたので、そのままいつものメンツで椿屋珈琲店にいってきました。

椿屋珈琲店内 椿屋珈琲店内
チーズケーキ

チーズケーキセット。1500円だっけかな。
例によって金箔がかかってます。
おいしいですよ。
チーズケーキ

2013年5月6日月曜日

リアルタイムプログラミングの最適化

というわけでリアルタイムシステムの前提が確認できたところで技術的な問題ーー実際の最適化手法の話に移りましょう。

遅い命令を速い命令に変える


アナクロな話題ですが、たとえば除算命令は大抵のプロセッサで遅いことが解っています。
だからわざとらしい例では、

x = y / 8;

は共に整数型である場合は x = y >> 3 ; に置き換えられることが解っています。
ですが、さすがにこれくらいの最適化はコンパイラが自動で行ってくれるというのもまた事実です。
しかし x, y が浮動小数点型であった場合は話が別です。

では x = y / 2; より x = y * 0.5; がよいというのは本当でしょうか?
これは昔から言われていることですので事実はどうあれ、手が勝手に x = y * 0.5 と書いてしまう気持ちはわかります。
浮動小数点なら x86 では fdiv/fmul 命令(最近は divss/mulss) が使われるはずで、 fdiv は fmul より 30 cycle ほどの多いレイテンシがあります。
ですから大抵の場合は本当です。
しかし定数の事前演算はコンパイラにとって得意な最適化の部類であります。これくらいであれば最適化コンパイラが使える場合は自動で最適なコードが出力されると考えられます。


整数の除算、整数/浮動小数点のキャストに気をつける


ほぼ ARM 固有の話です。
ARM が整数の除算命令やキャスト命令を持っていない(いや、コプロにはあるよ)せいで、コンパイラの開発者は泣いています。
コンパイラの開発者が泣く時、利用者も泣くのです。
なので整数除算とキャストをしれっと C のプログラマが書いてしまうと、コンパイラは代理のコードを一生懸命出力します。
で、これが大抵遅いです。 gcc をお使いなら、ライセンスを一通り読んでください。 GNU Runtime Exception という特殊条項でしょう?これの解釈だって残念ながら一つじゃありません。……おっと、これは最適化よりも厄介な問題ですね。

浮動小数点コプロにしましょう。NEON 命令セットだったらキャストなんか一発なのです。
逆に言えば、ベクトル化しなくても SIMD 命令を使って最適化できる余地は案外あるよということなのです。
この場合はコンパイラに NEON 使用許可を出せば、コンパイラがいい感じにやってくれるでしょう。
NEON がないプロセッサもターゲットにしているなら……残念。他のところで速度を稼ぎましょう。

一つの命令で複数データを処理する


y0 = x0 * a;
y1 = x1 * a;
y2 = x2 * a;
y3 = x3 * a;

という例を考えましょう。全て float (単精度浮動小数点)とします。
これは乗算の右辺が変数ですのでコンパイラによる最適化はそもそも期待できません。
4回の乗算ですが、例えば、 [x0, x1, x2, x3] * [a, a, a, a] というベクトル演算と捉えれば一回の乗算と同じです。
こういう時は SIMD 命令の利用を検討しましょう。
x86 なら SSE2/AVX, ARM なら NEON, PPC なら Altivec です。

SIMD は Single Instruction Multi Data の略で一つの命令で複数データを処理できる命令体型です。
(お望みであれば一つのデータだけを処理することもできます)
具体的にコレはベクトルデータのことを意味します。
例えば、ピクセルデータの処理には 8bit 整数の 16 要素ベクトルから 16bit 8要素ベクトル、 32bit 4要素ベクトルまで幅広く使います。
普通の幾何学ベクトル演算なら 32bit 浮動小数点の 4 ベクトルでしょう。
128bit ベクトルレジスタを持つプロセッサなら 64bit 倍精度浮動小数点 2 要素ベクトルまでですが、 256bit ベクトルレジスタを持つ AVX 対応プロセッサなら 64bit 倍精度浮動小数点 4 要素ベクトルもいけます。

清水さんの4次補間関数での例を見てみましょう。

元のコードは C++ で書くとこんな感じであります。
(stroke オブジェクトへの add() 操作はシリアルバッファへの書き出しという風にぼかしていますが、当たらずとも遠からずってところでしょうよ)

    for(int i = 0; i < num; i++, deltaT += invertNum){
        x =  ((xfact1n + xfact2) * deltaT + xv0) * deltaT + xp1;
        y =  ((yfact1n + yfact2) * deltaT + yv0) * deltaT + yp1;
        p =  ((pfact1n + pfact2) * deltaT + pv0) * deltaT + pp1;
       
        xfact1n += xFact1step;
        yfact1n += xFact1step;
        pfact1n += xFact1step;
        //stroke.add(x,y,p);
        *(buf ++) = x;
        *(buf ++) = y;
        *(buf ++) = p;
        *(buf ++) = 0.f;
    }

SSE2 命令を使ってこれを最適化します。
使いたい命令が解っている場合、イントリンジックス (intrinsics) というものを使って C/C++ のコードで命令を指定することができます。

gcc の場合、 emm_intrin.h をインクルードしてください。

    __m128 fact2 = {xfact2, yfact2, pfact2, 0.f};
    __m128 v = {xv0, yv0, pv0, 0.f};
    __m128 p1 = {xp1, yp1, pp1, 0.f};
    __m128 fact1 = {xfact1n, yfact1n, pfact1n, 0.f};
    __m128 step = {xFact1step, yFact1step, pFact1step, 0.f};
    __m128 delta = _mm_set1_ps(0.f);
    __m128 inv = _mm_set1_ps(invertNum);

    for(int i = 0; i < num; i++){
        __m128 r = _mm_add_ps(_mm_mul_ps(_mm_add_ps(_mm_mul_ps(_mm_add_ps(fact1, fact2), delta), v), delta), p1);
        delta = _mm_add_ps(delta, inv);
        fact1 = _mm_add_ps(fact1, step);
        *(((__m128*)buf) ++) = r;
   }

_mm_add_ps() は二つの float4 ベクトルの加算、 _mm_mul_ps() は float4 ベクトルの積算です。
積和命令が大抵あるんですけど……SSE には見当たりませんね。
_mm_set1_ps() は所謂 splat 命令で、スカラー値をベクトルスロットへコピーします。
でも、意外と簡単でしょう?

ベクトル化前のアセンブリコードは、ループ内だけで以下のようなものでした。

LBB1_2:
    movaps    %xmm4, %xmm9
    addss    %xmm10, %xmm9
    mulss    %xmm5, %xmm9
    addss    %xmm0, %xmm9
    mulss    %xmm5, %xmm9
    addss    -24(%rbp), %xmm9
    movss    %xmm9, -12(%rbx)
    movaps    %xmm2, %xmm9
    addss    %xmm8, %xmm9
    mulss    %xmm5, %xmm9
    addss    -12(%rbp), %xmm9
    mulss    %xmm5, %xmm9
    addss    -20(%rbp), %xmm9
    movss    %xmm9, -8(%rbx)
    movaps    %xmm1, %xmm9
    addss    %xmm11, %xmm9
    mulss    %xmm5, %xmm9
    addss    %xmm6, %xmm9
    mulss    %xmm5, %xmm9
    addss    %xmm3, %xmm9
    movss    %xmm9, -4(%rbx)
    movl    $0, (%rbx)
    movss    -16(%rbp), %xmm9
    addss    %xmm9, %xmm4
    addss    %xmm9, %xmm2
    addss    %xmm9, %xmm1
    addss    %xmm7, %xmm5
    addq    $16, %rbx
    decq    %rax
    jne    LBB1_2

ベクトル化の後は以下のようにシンプルになります。

LBB1_2:
    movaps    %xmm2, %xmm3
    addps    %xmm6, %xmm3
    mulps    %xmm1, %xmm3
    addps    %xmm8, %xmm3
    mulps    %xmm1, %xmm3
    addps    -32(%rbp), %xmm3
    movaps    %xmm3, (%rbx)
    addps    -48(%rbp), %xmm2
    addps    %xmm0, %xmm1
    addq    $16, %rbx
    decl    %r14d
    jne    LBB1_2

なんか無駄にスタックからロードしているのは気に入らないですが。
この場合はスタック以外には buf のアドレスにしかアクセスしてませんし、これを修正した程度ではベンチマークに影響なかったのでよしとしましょう。

本来はレジスタを使い切るまでループアンロールすべきですが、今回はループ回数が補間する点の距離に応じてダイナミックに変わるためそこまではしないものとします。
[x1, x2] - [y1, y2] として[1,1]-[1000000, 1000000] のような極端なデータを与え、 282843 回のループになるようにしています。これを10回ループさせて簡単なベンチを取得しました。

すると、ベクトル化前に 11ms だったコストがベクトル化後には 5ms になっていました。2.2倍も速くなったわけです!
Sandybridge の i7 ではそうだったというわけで、例えば NEON を実装した ARM ではもっと大きな伸びが予想されます。
経験的には 2.5 倍はカタいです。(もっとも NEON の実装がしょぼく、殆ど速くならないバカ実装のバカプロセッサもあるにはありますが……。その辺で売ってるようなやつは大丈夫でしょう)

でも実際に使うには飽和演算や、ベクトルレジスタへのロード/ストアについてはアドレスアラインメント,  permute (ベクトル要素の並べ替え) や splat など結構事前に要求される知識があるもんです。
そのへんには注意してくださいね。

ところで蛇足になりますが、ベクトル化する前のコードをよく見ると、


        *(buf ++) = 0.f;

という謎の行が追加されてます。  stroke.add() が未定義なのをいいことに、単純な比較が可能なように追加させてもらいました。
ベクトル命令は 128bit レジスタ前提なので 16byte ごとにしか読み書きできないからです。
メモリの読み書きを 16 の倍数にする(メモリのアドレスも)というのはベクトル最適化ではごくごく当たり前のことです。
ミスアラインアクセスも不可能なわけじゃないですが、アラインがマッチする場合についてベクトルレジスタの読み書きでは二倍程度遅いと思っていてください。

ループをアンロールする


ループを展開して条件分岐を削減します。
ですがループのアンロールが最適化にとって有意かどうか、実は常に議論の対象となります。
なぜなら条件分岐の予測はプロセッサにとって重要なテーマですし、昨今のプロセッサにとっては多くの場合ブランチミスプレディクションよりロードストアのほうがずっとパフォーマンスにインパクトがあるから無意味なことも多いのです。

for (int i = 0; i < 32; i ++)
   foobar(x[i]);

という極めて単純なパターンでは、インライン展開しないとした場合、何回ほどループを展開すべきでしょうか。
仮に4とすると

for (int i = 0; i < 32; i += 4) {
   foobar(x[i]);
   foobar(x[i+1]);
   foobar(x[i+2]);
   foobar(x[i+3]);
}

となります。この場合条件分岐の回数は1/4になります。
しかしながら関数の呼び出し毎に x の配列から値をロードしているのはそのままであることに注意してください。

コンパイラはループ内のコードが充分小さいなら結構積極的にループを展開します。ループ内のコードの量は、分岐予測までの最低サイクル数、命令キャッシュの量に依存します。
そしてループ回数が可変の場合も、予測が可能である場合かなり頑張って予測します。

つまりループのアンロールは考慮すべき条件が多過ぎて、コンパイラに任せるのが無難です。
しかしながら、ある条件下ではプログラマが自分で展開したほうがよい場合もあるのです。
どういう場合でしょうか?
それは以下のような場合です。
  1. ロード/ストア回数を削減できる場合
  2. レジスタが余っている場合
1の場合、要するにメモリアクセスが実は極めて遅くなり得るという事実に注目した最適化です。

たとえば上記のループでは

for (int i = 0; i < 32; i += 4) {
   register int x0, x1, x2, x3;
   x0 = x[i];
   x1 = x[i+1];
   x2 = x[i+2];
   x3 = x[i+3];   foobar(x0); foobar(x1); foobar(x2); foobar(x3);
}

というようなコードが書けるかも知れません。命令のプリフェッチは別ですが、メモリからのロードを1/4にできたことを意味します。
もっともデータキャッシュは一般に16バイト以上ありますので、キャッシュアウトしてなければこんなことをしなくてもそこそこ速いのですが、キャッシュが汚染されるような場合にはこちらのほうが速いことがあります。
この例ではループを展開したことに大した意味はありません。
しかしながら別の方法を併用することでスループットを増大することができるのです。
次のループで使うデータを先にロードしておくといった工夫でスループットを上げる方法や、場合によってはキャッシュ制御命令を使ってストリームロードを行うなど様々な工夫を施すことができます。

当然、データの先読みというのは保存しておくためのレジスタが充分余っていることが前提です。
レジスタが沢山あるプロセッサの場合はぜひ検討しましょう。
例えば、SPU にはレジスタが128個ありました。そこまでではなくとも SSE2, およびSandybridge 以降搭載されている AVX 命令では XMM/YMM レジスタを 16 個利用できますし、 NEON では 16個のベクトルレジスタを ABI に配慮することなく自由に使えます。

ところで、上記のようなデータの先読みをコンパイラが自動的に行うことはできないのでしょうか?
残念ながら、基本的にはできません。先読みに関してはプログラマが明示的に最適化を行わねばならないことが殆どです。
なぜなら foobar() 関数の副作用で x の配列の中身が書き換えられるかも知れないからです。
x がグローバル変数で、 foobar() から見えるならコンパイラは先読みを行いません。
詳しくは後述します。

……とこのように細かい話が続くと「ああ、じゃあコンパイラ任せね」と思うかも知れません。
ところがあなたが使っているソフトウェアの殆どは、こういう最適化を行っています。
ブラウザが使っている cairo を始め、某ブラウザプラグインもそうです。基本的に画像を扱うソフトウェアでループのアンロールを用いて複数ピクセルを一度にロード/ストアする最適化をしていないソフトというのを、僕は見た事がありません。
画像を扱うソフトはほぼ100%、4pixel (時には 16pixel) 同時処理するためループをアンロールしています。
でもその動機はブランチミスプレディクションを減らすためではなく、データのロードストアを減らし、ストリーミング命令を駆使し、キャッシュのヒット率を上げてスループットを向上させるためです。
お間違えなきよう。

インライン展開する


ループアンロールに関する話は少々難しかったでしょうが、インライン展開は簡単です。
C++/C99 では関数をヘッダに書いて inline と関数の前に書くだけです。
マクロと同じではありません。マクロは必ず展開されますが、インライン関数は展開されないかも知れません。
マクロは関数ではありませんが、インライン関数は関数です。(この違いは実は大きいもので、マクロではリンクエラーになるようなコードもインライン関数では平気です)
インライン関数は、昔の VC のコンパイラがだらしなかったせいで未だに色々都市伝説がありますが煩わしいのでここでは無視します。

間抜けな会社のコーディングルールではヘッダに実装を書くことを禁止していることもあるでしょうが、そういうところでは仕事をしないほうがいいと思います。

ただやはりインライン展開されて良いコードとされて困るコードというのはあります。あまり大きなコードは避けるべきです。
小さい関数であれば、インライン展開することで飛躍的にコードサイズを減らす事もできるのです。
ABI のコーリングコンベンションという、関数を呼び出すために守らなければいけない決まりというのがプラットフォームによって決まっていまして、これが余りにも厳しいような場合では、積極的にインライン展開してかまいません。
インライン展開されたコードはもはやコーリングコンベンションを無視したものです。コードサイズの削減と同時にパフォーマンスも向上します。
個人的な経験では、PPC でインライン展開を完全に禁止すると 10MB くらいになってしまうコードが、インライン展開をすることで 9MB 程度まで押さえられたこともあります。

PPC 以外でそこまで ABI が大きい例というのは知りませんが、 ARM や x86 であっても、ただの薄いラッパ関数などなら迷わずインライン展開すべきです。
リーフ関数の場合は呼び出し側の削減にしかならないので(コードサイズという点では)逆に大きな効果がないかも知れません。

小さいコードは効率がよいと思われるかも知れませんが、逆ということがあります。
例えば他の関数を呼び出すだけの関数といった薄いラッパーでは、コンパイラはそれ以上最適化する余地も殆ど残されていないのです。
レイテンシーを隠蔽するために命令同士を数サイクル離すのは(アセンブリレベルの最適化において)基本中の基本ですが、そもそも命令が絶対的に少なければコンパイラは諦めてしまいます。
かといってあまりに巨大なループは命令キャッシュから外れ、インライン展開により巨大になった関数ではプロファイラの運用やデバッグを困難にしてしまいます。

適度な大きさを保つことが重要です。
ラッパーやリーフから積極的にインライン展開し、マクロよりもコンパイルオプションで展開を制御できるインライン関数を使いましょう。


エイリアッシングの問題を避ける


void copy(void* dst, void* src, size_t len)
{
     void* end = (char*)dst + len;
     while (dst < end)
          *(((char*)dst) ++) = *(((char*)src) ++);
}

なーんにも考えずに書いたコピーのコードです。とりあえず前述のループアンロールの指針に基づいてループを展開しましょう。


void copy4w(void* dst, void* src, size_t len)
{
    void* end = (char*)dst + len;
    while (dst < ((char*)end + 4)) {
        *(((int*)dst) ++) = *(((int*)src) ++);
    }
    while (dst < end) {
        *(((char*)dst) ++) = *(((char*)src) ++);
    }
}

(見易さのためにアラインメントの評価をさぼっているのでこれはダメなコードですがそれはさておき……)
さて、このコードは期待通りのパフォーマンスが出るでしょうか?
ロードストアがレジスタ長に一致するのでたぶんパフォーマンスは出ます(というか下がらなくなります。ですが最適化前の copy() とは意味が違っています
なぜかというと、 dst, src が示す領域に重複した部分があると動きが違ってしまうからです。
このことは memcpy と memmove では memcpy は最適化によって高速化できるが memmove はそうではないという事実に結びついています。
(最適化できる可能性がある、というだけで全ての実装で memcpy のほうが速いとは限りません。同じかも知れません)


同様の問題を更に見てみます。

void copy4(void* __restrict__ dst, void* __restrict__ src, size_t len)
{
    void* end = (char*)dst + len;

    while (dst < ((char*)end + 4)) {
        *(((char*)dst) ++) = *(((char*)src) ++);
        *(((char*)dst) ++) = *(((char*)src) ++);
        *(((char*)dst) ++) = *(((char*)src) ++);
        *(((char*)dst) ++) = *(((char*)src) ++);
    }
    while (dst < end) {
        *(((char*)dst) ++) = *(((char*)src) ++);
    }
}

これは愚直にインライン展開した場合です。
展開した部分のアセンブリを見てみましょう。

LBB2_3:
        movb    (%rsi,%r8), %r9b
        movb    %r9b, (%rdi,%r8)
        movb    1(%rsi,%r8), %r9b
        movb    %r9b, 1(%rdi,%r8)
        movb    2(%rsi,%r8), %r9b
        movb    %r9b, 2(%rdi,%r8)
        movb    3(%rsi,%r8), %r9b
        movb    %r9b, 3(%rdi,%r8)
        addq    $4, %r8
        cmpq    %r8, %rcx
        jg      LBB2_3

レジスタへのロードからメモリへのストアが近過ぎます。
ロードしたレジスタを実際に使うまでに命令を稼いでレイテンシを隠蔽するというアセンブリの最適化の基本方針が生かされていません。
レジスタはまだ6つ余ってますので、どうにかならないのでしょうか。
せめてこんな感じ
        movb    (%rsi,%r8), %r9b
        movb    1(%rsi,%r8), %r10b
        movb    2(%rsi,%r8), %r11b
        movb    3(%rsi,%r8), %r12b
        movb    %r9b, (%rdi,%r8)
        movb    %r10b, 1(%rdi,%r8)
        movb    %r11b, 2(%rdi,%r8)
        movb    %r12b, 3(%rdi,%r8)
の最適化をコンパイラが勝手にやってくれてもいいのになぁ〜〜と思うかも知れません。
ですがこれも、前述した src/dst が互いに重複したアドレスを指しているかも、という可能性のために、コンパイラは決してこのような最適化を行いません。
この例では、 mov の順番を多少入れ替えたところでレイテンシを隠蔽する効果は殆ど期待できませんけど、もし仮に効果があるとしても、コンパイラは最適化しません

同じメモリ領域を指す複数の異なるポインタをエイリアスと呼び、最適化においては重要なテーマです。
エイリアッシングが発生すると、コンパイラは(仮に可能だったとしても)最適化を行えなくなるのです。

こういうときには、 __restrict__ キーワードを使って src と dst が互いにエイリアスとはならないことをコンパイラに示します。
(もちろん、本当にエイリアスにならない場合だけです。仕様上エイリアスを許容する memmove ではだめです)
void copy4(void* __restrict__ src, void* __restrict__ dst, size_t len)
{


エイリアッシングによる不要なロードを避ける


再びエイリアッシングの話題です。
話を簡単にするために、まず簡単な例から見てみます。
これも少々わざとらしい例ですが……。

void copyi(int* dst, int* src, size_t* len)
{
    size_t l = 0;
    for (size_t i = 0; i < *len; i ++) {
        *(((int*)dst) ++) = *(((int*)src) ++);
        l ++;
    }
    *len = l;
}

長さをアドレス渡ししています。これくらいは戻り値で返せよ!と思うでしょうが、まぁお付き合い下さい。
アセンブリを見てみましょう。

LBB4_2:
        movl    (%rsi,%rax,4), %ecx
        movl    %ecx, (%rdi,%rax,4)
        incq    %rax
        cmpq    %rax, (%rdx)
        ja      LBB4_2
LBB4_3:
        movq    %rax, (%rdx)

len に渡したアドレスは rdx に入ってます。rsi が  src, rdi が dst です。
大体予想通りかと思いますが、もしかすると一点奇妙なロードがあるとお思いかもしれませんね。
        cmpq    %rax, (%rdx)
copyi() の len は一回渡ったら関数の実行中に変更されないはず。
ならなんで比較の度にわざわざメモリから値をロードしているのでしょうか?
まぁ、 C のコードが自分で len から値を引っ張ってループの比較に使えばよいのですが……コンパイラの最適化とはこの程度のこともできないのでしょうか?
実はコンパイラは目ざとくも、ここでもエイリアスの問題が発生する可能性に気付いているのです。
len が dst のエイリアスーーつまり、 len のインスタンスが、 copyi() が書き換える dst の領域に配置されている可能性があることを気にして毎回ロードしているのです。
つまり呼び出し元が
copyi(dst, src, (size_t)dst[0]);
とやらかしているんじゃないか、という配慮ですね。
この場合も __restrict__ キーワードが使えます。
void copyi(int* dst, int* src, size_t* __restrict__ len)
{
としてコンパイルした結果をご覧ください。
        movq    (%rdx), %rax
        testq   %rax, %rax
        je      LBB4_4
        xorl    %ecx, %ecx
        .align  4, 0x90
LBB4_2:
        movl    (%rsi,%rcx,4), %r8d
        movl    %r8d, (%rdi,%rcx,4)
        incq    %rcx
        cmpq    %rcx, %rax
        ja      LBB4_2
LBB4_3:
        movq    %rcx, (%rdx)
        popq    %rbp
        ret

len はループに入る前に rax にロードされ、 cmpq で比較する際にいちいちロードされたりはしません。

まぁでも、こんな変なコードは書かないから俺関係ないよ、という方がほとんどでしょう。

では以下の例はどうですか?

struct wstr {
    short len;
    short* ptr;
};

void copywstr(wstr& dst, wstr& src)
{
    for (short i = 0; i < src.len; i ++)
        *(dst.ptr ++) = *(src.ptr ++);
    dst.len = src.len;
}

ずっとありそうですね。
事前に short l = src.len; としておくより1行少なく、よっぽどスマートなコードに見えます。
……が、これもエイリアスの問題を踏むのでコンパイルした結果は残念ながらループのたびにロードをしています。
小さいコードが効率よいとは限らないのです。
(無駄かどうかは本当にエイリアスを意図したかどうかによります)

エイリアスの場合の振る舞いを正しくするため、 STL の実装では意図的に上記のようなものが多いように思います。
STL を使うなという話じゃありません。真似するときは本当にエイリアスの場合があるかどうか考えてやりましょうということです。

エイリアスの問題に慌てて取り組んだところで、プログラムの全体が何パーセントも向上することは期待できませんし、ホットスポットに対応するにはもっとずっと効果的な方法があるはずと考えるのが普通です。
それどころか、本来エイリアスを受け入れるべきところを潰してしまい、バグを生む可能性のほうがずっと高いです。

ですが——ARMのようなプロセッサで何百KBにもなるデータを処理する場合を考えてみてください。
ARM の実装により、 LRU キャッシュアルゴリズムを持たない実装というのが結構あります。そういうバカキャッシュのプロセッサでキャッシュの汚染が発生するというのはパフォーマンスを大きく下げる原因になります。
(4way キャッシュくらいあるのは普通なのでコピーくらいは救われるかも知れませんが、他に src/dst 以外に係数テーブルやらスタックやらで消費されるでしょう)
L1/L2キャッシュミスしたら数十usから百数十usのオーダーで時間が無駄になることを覚悟せねばなりません。それがループ回数だけ積み重なるとあっという間に 1ms なくなってしまう場合もあるのです。

遅いコードを速くするのは最適化の基本ですが、リアルタイムプログラミングにおいては遅くなる可能性を徹底的に排除しなければいけないコンテキストが存在します。
そのようなコードにおいては気をつけましょう。

-fno-strict-aliasing を回避する


最近の最適化コンパイラは、非互換型のキャストに際して発生する偽のエイリアッシングを無視して最適化することがあります。
これをこのまま使うと予期せぬバグが顕在化することも少なくありません。
というか、すごい発生します。

そこで、最適化を抑制するためのコンパイルオプションとして、 gcc では -fno-strict-aliasing というのがあります。
これは最適化を保守的にする代わりに、非互換キャストを行った場合でもエイリアッシングを無視した先読みをさせなくなり、ルーズなキャストまみれのコードが問題を起こすのを避けることができます。

経験上、オープンソースの大半のプロジェクトで -fno-strict-aliasing がついてます。
パフォーマンスの観点からは避けるべきです。
ホットスポットの翻訳単位から順番に外していけるようにしましょう。

エイリアッシングの問題はそもそもわかりにくく、更に非互換キャストでのみ発生する偽のエイリアスなんて人間が自分で見つけるのは困難です。

そこで -Wstrict-aliasing オプションでコンパイルすると、最適化を妨げるエイリアッシングを警告してくれるようになります。


インラインアセンブリの運用


インラインアセンブリを書いていると、解ってない人からは
「今時アセンブリなんて……バカじゃないの?」
と言われます。

一方、よく解っている人からは
「インラインアセンブリなんか使うな。フルアセンブリで書け」
と言われます。

アセンブリが時代遅れなんてのはそりゃー昔から言われてます。
最近の複雑化したプロセッサにおいて人間の書いたアセンブリがコンパイラの最適化に勝てるなんて、そういう理由から書くわけじゃないのです。
ピンポイントで、どうしてもここだけコンパイラの最適化じゃ足りないとか、どうしても使うべき命令が他にあるというような止むに止まれぬ事情から使うのです。

で、そういう状況はみなさんが思うよりずっとあります。
cairo, dlmalloc, avm+, webkit, fiber……名前は伏せるけど商用アプリケーションの数々、これらは全部使ってます。人気のあるソフトでインラインアセンブリを全く使っていない例というほうが少ないです。 zlib とか Freetype は使ってないかな。 STLport とか DB の実装とかでも使ってなさそう。
必ずといっていいほどどこかに登場します。

僕はデバッグ目的で書く事も多いのですけど、その目的は皆パフォーマンスのためです。
時代遅れと誹られつつも100%全員がインラインアセンブリのお世話になっている。

または、時代遅れだよなと鼻で笑った当人が裏ではこっそりインラインアセンブリを書いている。アイツも、……アイツもだ!

まぁ、そうはいってもコンパイラのイントリンジックスが充実しているお陰で、本当にインラインアセンブリを書かねばいけないケースというのはだいぶ減ってきたと思います。

ところで、本当に解っている人がインラインアセンブリを嫌う理由は何でしょうか?
それはインラインアセンブリのオペランド制約が gcc info を見ても載っておらず、 gcc のソースを見てその通りにやっても上手くいかずイライラすることがとても多いからです。
コンパイラとプロセッサ、そしてそれぞれのバージョンへの依存がとても大きいのですね。
逆説的ですが、保守のことを考えるとインラインより関数全部をアセンブリで書いたほうがマシ、ということすらあり得ます。

じゃあ実際、どういうときに使うの?ということをここでは書いておきます。

ビットを数える

値の何ビット目が立っているか?値の中に立っている立っているビットはいくつあるか?
そういうことを調べたいのはよくあります。
hacker's delight の実装を使っている例もありますが dlmalloc などではプロセッサの命令を使うようなコンフィギュレーションも存在します。
popcount 命令や bcl/bcr 命令なんかですね。
x86 ではイントリンジックスにありますので不要でしょう。

SIMD命令

計算そのものはイントリンジックスで行えることが殆どですが、特定レジスタへの値のロードなどではイントリンジックスが使えないもあります。
そもそもイントリンジックスがなければしょうがありません。
例えば AVX 命令を使いたい!と思っても手元の gcc4.2 では対応してません。
gcc を気軽にアップデートできない事情は皆さんよくご存知でしょう。
そういうときには活躍します。

スピンロック

マルチスレッドプログラミングにおいて、マシン固有のスピンロックは人気があります。
もはやマルチスレッドプログラミングでは必須といってよいでしょう。
mutex/semaphore などのシステムコールと異なり特権モードにスイッチする必要がないので非常に軽いです。
(その代わり使うときにはデッドロックに注意せねばなりませんが)

大抵の場合、イントリンジックスにはなくてそれ以外の組み込み関数にはあるかも知れません。
処理系が持っているならそれを使いましょう。だってテストするの面倒じゃないですか。狙った通りにバスロック横取りしたりキャッシュラインロックロストしないとならないんですから。
でも、そうでない場合や処理系依存の実装を使いたくない(スピンロックを使う以上、可及的速やかにクリティカルセクションを通過する場合なんですからそういうこともあるでしょう)場合自分で作るほかありません。

ABIを無視、またはABIに強烈に依存するコード

余程レアケースとは思いますが VM の実装なんかでは結構あります。
レジスタのアサインメントは ABI (Application Binary Interface) により固定なので、コーリングコンベンションを利用したデバッグルーチンや特殊なサンク関数などはアセンブリで書くよりありません。



また騙された話

ここ二年ほど、 3d-coat というアプリを使っていまして。
モデリングは Lightwave のキーバインドが骨身に馴染んでいて角張ったものから有機的なものまで Lightwave 一本で効率よくモデリングいけるのですが、テクスチャとなると気が重い。
なんせ Lightwave はモデリング以降のフローにおいてサードパーティプラグインへの依存度が非常に高い。
僕は Mac 版ユーザー。こうしたプラグインは Windows 版のみの対応ってのが多いので、プラグインが不足している感は否めない。本格的に高速なレンダリングを目標にそこそこのディティールを持つ有機体……となるとちょっと運用が厳しい。
そこで 3d-coat。
こいつはとても安価なスカルプトソフトです。買ったときは最初 $250 くらいだったかなぁ?
ボクセルベースを特徴としていますが、最新版はサーフェイスモードもサポートしています。
ややワークフローが硬直しており、そこは慣れが必要ですがとにかく出来がよく、スカルプト、リトポ、UVラップ、テクスチャの焼き込みまでをかなり快適に行うことができるのですね。
Mac 版もあり、プラグインへの依存が少ないので問題なく利用できます。 Mac 版と Windows 版での差異はせいぜい、 CUDA のエンハンスメントが使えないことくらいです。
しかしこの 3d-coat の最大の良さはサポートの良さではないかと僕は思います。
フォーラムでの開発者のリアクションは、いったいどこからこのモチベーションが湧いて来るのかと心配になるほど速く、ベータ版リリースサイクルは遅いときでも二週に一度程度、早いときは週に二度、三度といったペースです。
もちろん自分の踏んだバグを一番のプライオリティで直してもらえるわけではないけど——そこは技術者として詳細なデバッグ情報を提供して「たぶんここが間違ってる」まで言えれば速攻で直ります。
(こういうとき、 mac 版なら即座に gdb で重要な情報だけを抽出して提供できる)
本業でもこんなスピード感はなかなか体験できない。提供するのもどれほど難しいかよくよく知ってます。それだけに素晴らしい。
ワークフローごとに機能の重複があるので、この辺の機微を巡ってはフォーラムで開発者とパワーユーザーが衝突することもあるのですが、殆どあらゆる点で僕はこのソフトと開発者たちを尊敬しています。


 

悪い例


素晴らしいソフトもあればあまりにそうでないソフトもある——。

Project messiah をご紹介しましょう。

誤解なきよう最初にお断りしておくと、 messiah は優れた機能を持つソフトです。
高速且つコントロールし易いクロス/ソフトボディシミュレーションや GI レンダリングは他の追随を許さない気がします。
こんなに素晴らしい機能に恵まれていなければこんなソフトは早々に捨てるが、それができないくらいにはこのソフトの *機能だけは* 素晴らしい。

機能以外での悪い点が多過ぎます。
  • Mac 版は重要な機能が使えず、運用不能
  • ライセンスが不安定。動きさえすれば御の字
  • バグがあってもまずパッチは出ない。レポートは無視される。運が良ければ次期有償アップグレードで直るかも……
  • サポートなし。困っていても基本時間切れ狙い
  • ロードマップ不明
  • ドキュメントが雑
  • 詳細な説明は動画で。しかも別売り。
  • あらゆる点でベンダーとしての説明なし
一目で「そりゃーないよ」ってのもありますが、まぁ、説明していきましょう。

messiah は、Lightwave 開発チームからスピンアウトした人達が開発するソフトです。
Lightwave を反面教師とし、且つ相互運用性が高さもウリになっています。
当初、 Lightwave の Layout はもう終わった、これからは messiah があるとまで言われたものです。

フリーウェアでしょうか?いいえ、商用ソフトです。
Pro 版は $499 だったかなぁ?結構いい値段でした。しょっちゅうディスカウントしてますのでフルプライスで買った僕が不幸なだけでしょうか。
その出来は……Auto Rig がデフォルトで face camera を生成する癖に face camera があると即ハングする時点でお察しというべきでしょうか。
face camera 消せとか四角ポリゴンは即死とか、その程度のことであればフォーラムに書いてあるかも知れません。

公式サイトには Mac 版があると書いてあります。スタンドアロンライセンスで、 Mac/Windows 版を個別に利用することはできません。

ところが Mac 版はなんと Crossover で動作する Windows 版です(ライセンスは別)。
まずこれだけでドン引きですけどパフォーマンスも良く、見た目がしょぼいだけでちゃんと動きます。逆に「最近の wine は本当凄いなぁ」と感心してしまうほどです。
ただやっぱりマウスの動き周りにどうしようもない齟齬が多く、ミドルクリックを超多用するインターフェイスなので Mightmouse では死亡します。
マウスの振る舞いはそこまでカスタマイズできず、逃げ道がない。
仕方がないので別のマウスを接続して使っているけれど、 wine のミドルクリックの対応が今ひとつなのかしょっちゅうおかしなことになってアプリの再起動が必要になります。
一応動くけどもとてつもなく使い難い。
それでも我慢して使っていくと……まぁモーション付けまでは意外にも結構使えます。

まずもってライセンスシステムが動かずに全く使えない人がフォーラムに沢山いる手前、幸運にも動作した僕はそれだけでハッピーとは言えましょう。
だってこれ、ライセンスの問題なのに開発者が全然対応しない。

messiah のウリは Lightwave との相互運用性が挙げられます。
でも messiah が Lightwave ブリッジに使うプラグインは Windows 版しかなく、Mac では事実上この機能は死んでいます。
僕はこの点については宣伝に致命的な偽りがあると問題視し、サポートフォーラムで文句を言いました。

僕が知る限り、サポートフォーラムでは開発者が返事をすることはまずありません。
スレ立てして逃げます。まるで釣り堀です。
返事は開発者に代わって、初期ユーザーの人が一人で頑張って返事をしますが、ほぼ返事になってません。
よっぽど FAQ でない限り問題を解決できる可能性は限りなくゼロに近い。
僕が Mac 版では Lightwave と相互運用できねーじゃんよと文句を書き込んだところ、その一人の初期ユーザーから「FastMDD というプラグインを使えば良いよ」といわれました。

Fast MDD は MDD ファイルを生成、さらにそれをパイプとして利用し、本来 messiah が行うべき Lightwave との HUB として利用できるプラグインです。
別売り。
$40。
ふざけてんのかと思いつつもこれを買いました。
ですが Fast MDD はバグっており、やっぱり使えませんでした。
(ちなみに個別にライセンシングされている。このライセンスが動かないという人もしばしばいて、フォーラムでの解決例は知りません)

messiah もそうですがそのプラグインもロクにテストせずに売ってます。
この人達は皆エンジニアではありません。アーティストなのです。
プラグインの開発者だからといってエンジニアと思って話をすると面倒なことになります。
まぁ、それでもなんとかバグってて使い物にならん状況を伝え、直すと確約させました。
半年経ち、どうなったか訊ねるとようやく直したと言います。一年経ち、どうなった訊ねるとようやくテストを始めました。それから更に一年以上経ちますが、未だに修正版はリリースされません。

どうしようもねーこいつらと思って放置してたのですが、その間もフォーラムにはたくさんの苦情が書き込まれています。
大半のユーザーは Mac 版が Crossover であることに怒っており、怒っているんだけど彼らは殆どの場合、具体的になにがまずいのか上手く伝えられない。
流れとしては大体以下のような感じ。
 モデレータ(M)「いや、 Crossover は優れてるよ。何にも問題ない」
ユーザー(U)「問題あるんだけど」
M「とにかく wine は素晴らしいよ」
U「……」
M「wine は優れてるのになぁ」

モデレータ(正確にはモデレータじゃないが、返事をするのは基本的に彼しかいないので便宜上モデレータとします)の主張は解る。確かに、 wine は僕らが思うよりずっと優れている。アレルギーを起こす前にちゃんと評価すべきだ。
だがそれは開発者にもまったく同じ事がいえる。
Wine は素晴らしいけど、 messiah は Mac 上でちゃんと使えるだけのテストを行っていない。Wine はエミュレータじゃないのだから、まったく同じにならないのにまったく同じになると前提して商売している。
そもそも一部機能が使えないことに対応がないばかりか、それについて何の注意も説明も行っていない。
フォーラムに書き込まれる報告について大半はモデレータ兼パワーユーザーの"まったくみんなどうかしてるよ"とのボヤキや、内輪受けみたいなジョークで終わることについてもっと深刻に考えたほうがいい。

そしてまた最近にも同じ話題が繰り返されていました。

U「Ver6 はまだ Crossover なのか?ネイティブにしろよ。まったくがっかりさせるぜ」
M「Mac はしょっちゅう OS が変わって互換性が崩壊するからネイティブサポートはできないよ」
U 「問題なきゃいいよ。問題があるんだって」
M 「具体的に何が悪いんだい。 ちゃんとバグレポしたかい?」
U 「したよ。messiah じゃ普通のことだが、無視された
M 「Crossover はともかく Wineskin を試せよ。こいつは全く素晴らしいんだぜ」
U「俺アーティストだからわかんない。どうやって試すの」
M「Wineskin は最高なんだぜ。Crossover とは大違いさ」
U「だから、どうやって試すかって聞いてんの」
M「……」

モデレータの言い分も少しは解る。 Mac はしょっちゅう OS の互換性が崩壊するよね。 (彼が別に述べていた「apple はプロユーザーを放置してる」ってボヤキも、ソフトベンダが mac サポートを熱心にできない理由にはあるだろう。だけど世の中、同じ分野で 3d-coat みたいなサポートをしているベンダだってあるんだぜ)
でもそれを全部 Wine が吸収してくれるかっていうとそうじゃないから、開発できないんだったらサポートもできない。開発できないサポートできないなら商売できなくね。ならそもそも間違ってたんだよ、前提が。

遡ること数ヶ月、今年の初頭には「messiah v6 が出ます!今なら$99でアップデートできます!」ってキャンペーンが行われていましたが、その v6 が

  • いつ出るのか?
  • どんな機能があるのか?

それについて全く記載がありませんでした。
実は同じ商売を Lightwave がやって思いっきりぶっ叩かれました。Lightwave は新バージョン CORE を発表し、早期ユーザーから開発費を集めて開発していたのに CORE の開発は頓挫。
結局、ユーザーにはちょっと修正しただけの 10.1 が送りつけられた経緯があります。
Lightwave は公式に謝罪して返金に応じました。
たしかに Lightwave の Modeler/Layout 分離のアーキテクチャはボーンを始めマテリアルの設定までかなり不自由なものでした。かといってコードをゼロから書き直すというのはそういうリスクがあることだよね。僕は文句を言わず 10.1 を受け取りました。

messiah は何の約束もなく金集めを始めたーーそんな風に見えました。
さすがはスピンアウト組だ、 LW を反面教師にしている、とね。
Lightwave  は挫折こそしましたが誠実でありました。 CORE への出資者への報告を怠らず、雲行きが怪しくなったときはちゃーんとヤバさを出してました。一方で messiah の不誠実さは折り紙付きですから。

先月始め、「messiah v6 がリリースされたよ!今なら $99 だ!」というメールが届いたときには「おお」と思ったものです。
メールのリンクから新機能ツアーを観に行くと、地味ながら効果的なフィーチャーでした。
そりゃメジャーアップデートって感じでもないけど……バグフィックスとかあるし……まぁご祝儀込みでアリかなぁというところ。
ただでさえギリギリだったのに OS は今や Lion ですし、 Lion にしてから動かしてないけどまず動かない気がするしさぁ、アップデートしとくかーと思ったわけです。

$99 払う。

そしてダウンロードページに行き、僕が見たものとは……!




か、か…、

かみんぐすーん。

リリースされましたってメールで言ってたじゃん。
俺がさー Windows 版のユーザーじゃないって知ってるよね? Mac 版のライセンスしか持ってないって。直メールで来たんだから。

フォーラムを見ると案の定お怒りの人がいました。4/3 のスレッドです。流れを意訳すると

U「おめーらさー、 messiah v6 がリリースされましたっていうから買ったんだよ? Mac 版のリリースがまだってどういうことだよ。 Mac 版は messiah v6 じゃないのかよ!?」
M 「落ち着いて。これはね、ソフトリリースってやつなんだよ。プラットフォーム毎に出すのさ」
U 「はぁ?それは開発者が言ってるの?どこで?なんて?このフォーラムを見ろよ、開発者による、たった一行の説明すらない。納得できないよ

時期も示さなきゃ説明もない。
そんないい加減なソフトリリースなんて聞いたことがありません。
なめてるとしか言いようがない。
いつも丸め込みに終始するモデレータも今回は精彩を欠く。 なんせこのユーザーのコメントは

しかもこの左側に書いてある説明文。

Lion のユーザーはインストールができません。
まず Windows にインストールしてから Mac に持って行ってください。ライセンスを移行?(coverting)できるようにします。

wineskinを拾って来て実際に試してみると、たしかにインストーラーすら満足に動作しません。
最新開発版でも最新安定版でも同じことでした。ログを見てもエラーらしいエラーは出てませんが、とにかく setup.exe がないというので msi インストーラーを正しく展開できてないようです。
(唯一出ているエラーメッセージは shared storage を未サポートだというもので、これは .net2.0 を使う限りは一通りでるメッセージのように見受けられます)

説明がないから想像するしかありませんが、つまりこういうことです。

まず彼らは Windows 版を作った。
彼らの中で wine は最高。どの Mac でも Windows と同じ環境ができると思っていた。
でも動かなかった。
wine は直せない。
だから mac 版のリリースは延期。
いつまで延期するか? wine が動くようになるまでに決まってる。
でも販売を延期するつもりはなかった。それらの説明なく販売開始。
はっきりと文句を言われても説明はしない。少なくともここ一ヶ月、一切説明なし。


ああ、いつもの messiah 開発チームだ。
でもリリースされていないということは、最低限インストールが出来るかどうかのテストくらいはしていたんだな。よかったよかった。

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 を使っていた人なら、リアルタイムシステムの難しさというのを身に染みて体感できたと思います。

最適化に関するetc たかが17msされど17ms

個人的に尊敬するUEIの清水さんが書かれていた最適化話、これは enchantMOON に特化した話であると思うのですが、内容はさておき twitter での反応は「ほんと?」ってのをちらちら目にしました。


本日見たところ、清水さん自身が追記をなされていましたがちょっと雑感を書いておこうと思います。

パフォーマンスの最適化については目的別に二種類あって、遅い処理を高速化することと、遅くちゃいけない処理を速くすること、です。
前者は非常に解り易い話、一般に最適化といったらこちらです。
こちらの場合は、まずはプロファイラなどを用いて本当に遅い箇所を見付け、対処します。
殆どの場合は何万回も同じコードが使われる場所があって、これをボトルネックと呼びますが、そこを最適化すると全体のパフォーマンスが大幅に向上することも少なくありません。
パレートの法則に倣い、コード中の20%を改善すればパフォーマンスが80%改善するさえ言われているものです。

最適化の鉄の掟は二つあります。

ボトルネックから優先的に行うこと
遅そうだ、速そうだという感覚でやらないこと


つまり必ず前後で測定しましょうということ。
こちらのほうが速そうだと思って書き換えたらなぜか遅くなったーーこういうことはよくあるものです。
ですので、上に引用したようなツイートは指摘としては至極真っ当なものです。
その一方、(別に引用しませんが)「そんなのコンパイラに任せていい」など的外れなツイートもありました。

よくある勘違いとしては、「近年の最適化コンパイラの吐くコードは素晴らしく、アセンブリレベルでは人間の手が入る余地がない」というものです。
前半はとても正しい。
確かに近年の最適化コンパイラの吐くコードはとても素晴らしい。だからといって人間の手を入れる余地がないとは限りません。
コンパイラが古い命令セットしか知らない場合もありますし、そこまで極端な場合でなくともコンパイラの吐くコードを見て初めて問題に気付くということもあるものです。
コンパイラは仕様に反するコードは吐かないから、プログラマが完全に言語と処理系の仕様を知らないと最適化には足枷がつくものなのです。

(まぁそれもこれも"最適化"コンパイラの話。 JIT などのコンパイラはコンパイル時間そのものもパフォーマンス要求として考えられますので、最適化なんてものは殆ど行われないのです。少し前と比べりゃほんの少しマシになったかも知れませんが、今のところはまだ最適化というより「いくつかのケースでは最悪を避けられるようになった」と考えるべきです)

いくつか実例を交えて説明することもできますが——今回のエントリのメインはその一般的な最適化の話ではなく、後者の「遅くちゃいけない処理を速くすること」のほうなので脱線する前に話を戻しておきましょう。

まず「遅くちゃいけない処理」とはどういうことか?
これは例えば実時間に関連した処理のことです。

  • ユーザーのインプットを受けて、次の画面の更新までに処理を終える
  • 50ms以内にPCMサンプルを1000サンプル処理する
などは実際の時間に追いつくように処理をしなければいけません。
仮に十分とか、そういう時間の単位で見た場合は予想時間内に収まるとしても、ある瞬間を見た場合に追いつけなければ、ユーザーがイライラしたり、音が切れて不愉快なノイズがスピーカーから出たり、マウスのポインターがカクカクしたり、そういう処理のことです。
こういうのをまとめてリアルタイム処理といいます。
リアルタイム世界では「1秒あたり100回処理するということは、一分間で6000回処理できればいいのね」というのは正しくありません。仮に一分後に6000回処理できているとしても毎秒100回の処理が50回になったり150回になっては困るのです。
過ぎた時間は戻らない。統計のトリックでは誤摩化せない。それがリアルタイムの世界。

enchantMOON はユーザーの入力を決して取りこぼさないよう、優れた書き味のために最適化を施しているとされますので、文脈からいって件の記事はリアルタイム処理を前提としたものだと考えていいと思います。
リアルタイム処理をリアルタイムで処理できないということはどういう結果を齎すか、幸いというべきか皆さんが日頃から体感できることですので別のエントリであげつらうとしてここではリアルタイムの最適化とそうでない最適化では話が違うということを解ってもらえれば充分です。

最も強調したいのは、こういう遅延はプロファイラでは観測しにくいものだってことです。
だってユーザーの入力が一秒間に何万回もあるはずはありません。サウンドのデータが一秒間に何百万サンプルにも増えるというのは(今のところは)ありません。
普通にプロファイラをかけて発見できるようなのはもっと長い時間単位での支配的な処理ですので、 vector の操作だったり、 list のつなぎかえだったり、システムコールの呼び出しだったりするものです。
だからリアルタイムの最適化ではプログラマの勘と経験がモノをいうことも多々あるのです。
そうした事情を踏まえてこそ件の清水さんの記事は面白く読めるのであって、「こんなの意味あんの?」と思って読むのはズレていると思う。
(もちろん、そういう遅延を発見するためのプロファイラの使い方テクニックはあるのですけどもね)

清水さんの記事には追記があって、最適化前後のデータが数値で表されているわけですけども、どこで誰が試しても23msが6msになるというわけではないから、数値として意味があるわけではないでしょう。これはそういう前提に立てない人向けに信憑性を高める意味であって、そういう前提に立てない人がこの数値を見たところでやっぱり本質的なところは理解できず、「なるほど速くなったね。でもそれって意味あるの?」とか言う気がする。
リアルタイムの処理では23msと6msは雲泥の差です。支配的なボトルネックでないなら、全体が4倍になるということはあり得ないけど、それでもこの17msが惜しいということはあるんです。
僕も20ms以上かかっていた処理を6msまで最適化したことがあるけども、これがどれくらいの差かというと BBC Internet Blog の Anthony Rose の記事で言及されるくらい。

——「でもそんなの、CPU が速くなれば問題ないでしょう?」
7-8年くらい前なら僕もそれに同意できたかも知れません。
でも、ここ数年 CPU の進化は止まっています。CMOS の低電圧化が限界になり、プロセスルールも頭打ちするなかでクロックは上がらなくなっています。ここ三年ほどは特に顕著に出ています。おそらく次の三年も状況は大きく変わらないでしょう。

と、前提を踏まえたところでリアルタイム処理のための最適化に関する細かい技術の話はまた別のエントリで、と致しましょう。

以下余談です。
CPU のクロックが鈍化し、遂には殆ど止まってしまったような昨今ですが反対にソフトウェアは仮想化が進んで、ネイティブコード以外の配布形態も多くなりました。
Android は Dalvik VM をアプリのランタイムにしているし、enchantMOON なんて JavaScript の VM をミドルウェアに据えているわけです。
ハードはそれほど速くなってないのにソフトだけどんどん重くなっちゃったわけですね。
背後にはモバイルプロセッサの飛躍があるわけですが、バッテリーに技術革新がなければそれも頭打ちです。

ソフトウェアの生産性のためには VM が素晴らしいのですけども、そのソフトウェアの生産性ってやつがユーザーの要求にマッチしなくなれば結局廃れる運命にあるでしょう。
JavaScript でアプリが書けるんだぜぇ〜と山ほど生まれて来た有象無象の web アプリで生き残っているのってなんでしょうか?結局開発者と投資家が喜んで、ユーザーは喜んでないのではないかという疑念はここ数年ずっと晴れてません。
僕だってユーザーだが、PC をアップグレードする理由は自分の仕事を速く済ませるためであって開発者に楽させるためでは断じてあり得ないわけです。
そのユーザーが「 Facebook アプリはネイティブで(僕らのいうネイティブとは違うけど)提供しろ」と言ったわけで、この意味は大きいと思うのですが……。
Twitter は web に回帰しつつあるけど、成功するかは解らない。
ソフトの生産性重視、実行時パフォーマンスの度外視といったトレンドはもうそろそろ古い常識になるかも知れないなぁ、とここ数年は考えるのでした。