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 に回帰しつつあるけど、成功するかは解らない。
ソフトの生産性重視、実行時パフォーマンスの度外視といったトレンドはもうそろそろ古い常識になるかも知れないなぁ、とここ数年は考えるのでした。

2013年4月15日月曜日

SIMCITY 2013 始めました

リリースされて1ヶ月以上経ってますが、 Mac 版の発売日が 2013/6/11 と決まったので早速購入しました。
Windows 版でも、既に買っていれば Mac 版も無料で遊べますので。

今まで僕はシムシティより A 列車で行く派、ポピュラスよりパワーモンガー派でして今回も別に「フーン」としか思ってなかったのですが、交通システムの多彩化といい、シムレベルの微細な観察が可能とあって一気に「うぉぉぉ」となったわけです。

シムには一人一人名前があって、どこからきてどこへ行くのか、職業は、収入は?とディティールがあります。
血を売ってでもスタジアムとセロリを求める、これまでののっぺりとした名無し野郎どもとは違いパワーモンガー派にはたまらんですよね。

早速プレイしてみなければ、ということで。
とはいえ我が家の Windows マシンなんて…。
X2 マシンはゲーム向きだけどディスプレイがいまどき 4:3 だし、うっかり Vista 入れてから起動する気すらしねえよというていたらく。
そういえば半ばジョークで買った i5 ノートがあったなぁ、と思い出したのですが Intel HD Graphics というチップセット内蔵 GPU で果たしてどこまで使えるもんだろうか……と。

結論から言うと、まぁ、動いてます。そこそこゲームにはなってます。
CPU 的には i5 もあれば充分っぽいです。グラフィックスはさすがに残念極まるところで、以下、お写真もそれなりなのでこれ見てガッカリしないでください。
テクスチャレベル、頂点数、ライティング、アンチエイリアス、全てが最低レベルです。
世の中、もっと素晴らしいスクリーンショットがゴロゴロしてます。

はいではどうぞ。


……縮小していればそこそこ見られますね。
それではどんなゲームか、ぜひご覧ください。

このマップは岬の一部を都市として開拓できます。
街の規模はかなり狭いです。ここに工業地帯や商業地域、住宅地、そしてゴミ処理場や上下水道施設を共存させていかねばなりません。
最初は高速道路が一本あるだけですが、最初はこれだけが人を呼び寄せる唯一の方法です。
道路は送電線、上下水道全てのライフラインを兼ねているので、高速を中心に道路網を拡張していきましょう。
治安、公害、そういった要素もありますが、実は交通網のほうがずっとキモだと思います。

治安とは例えば警察や消防ですね。
消防署を作って、消防車を必要数導入しないと街で火事が起きたときに眺めているほかありません。
そういうわけで消防署を作りました。
すると、消防士たちが花火をやりたいと言い出したので許可しました。
そうするとたちどころにこうなります。


花火がきれいだなー。
って隣の建築中の建物が燃えてますね。
燃えているのは、やっと着工した最初の高層建築です。完成したらデパートだったのでしょうかね。
消防士は花火に夢中のため、ビルは燃え続け、翌朝には消し炭になりましたが。
そういえば中国ではお祭りの花火で建築中の高層ビルが燃えたことがありますね。シャレになりません。


火事は実に厄介で、普段あんまり起きないので犯罪などと違って放置しがちです。
ちなみに消防署や警察署は地価に良い影響を与えます。
地価が上がるとお金持ちが住み、買い物するようになります。お店のグレードも上がって市の収入も増えるので重要ですよ。
お金持ちは自分より貧乏人向けの商店では働いてくれないので、労働力のバランスにも配慮する必要があります。
貧乏住宅地の近くに高級商店街ができたり、逆になってしまうのは好ましくありません。
従って後から消防署などを建てると、 このへんのバランスを崩してしまいがちです。

でも……


気をつけていても火事は起きるんですよね。
学校などを作って教育レベルを上げることでも、火事を減らす効果があります。
この地区には小さい小学校が一個しかなくて未就学児童が山ほどいますので、火遊びする輩も多いみたいです。

ちなみにこの火事、消防署の隣のブロックで起きました。
消防署三台が消火にあたりましたが、最終的にこのブロックはほぼ燃えてしまいました。
市長はメニューの中から一生懸命、八百屋お七を祀る神社を探したのですが、見つかりませんでした。

写真はこの時点で五万人程度の街です。
これくらいまで発展すると公害による水不足や電力不足が深刻になってきます。
このゲームは道路の密度を上げる(車線を増やす)と、周辺の建物が高密度化するシステムです。なので、

渋滞が起きる→道路を拡張する→高密度化する→インフラ不足

という流れで、あれよあれよという間にインフラがパンクします。
市庁舎に関連部署を設置することでより高度なインフラを使えるようになりますので、市役所増設に備えて役所の周りには建物を造らないようにしましょう。

あと、シムどもは公共料金を一切払わないようなので、税金でまかなうほかありません。
ああーーー民営化してえーーー水道と電気料金取りてーーーーと思いますが、そこは税率上げてがんばりましょう。
何をするにもお金は必要です。

大体のインフラは土地と金にモノを言わせて解決できるのですが、交通インフラだけはそうもいきません。
こればっかりは頭を使うしかないのです。渋滞してしまうと緊急車輛は通れず、シムは仕事に行く事もできずに商店や施設は閉鎖してしまいます。
以下の写真は失敗例です。

真ん中に商業地帯、右側に住宅地帯があります。
商業地帯のすぐ右側のカーブした道路が渋滞しているのがわかります。
朝からずっとこの調子ですので非常に大きな社会問題ですね。
シムシティでは道路は右側通行です。日本と逆なのでちょっと注意が必要です。
アパートを出たシムたちは一斉に右(写真上方)へ 向かい、左折しようとしますが、右側通行なので左折が厳しくなります。
ほとんどのシムたちは写真中央の商業地域で買い物や仕事をしようとしているのですが、そこまで二度左折するために大渋滞を引き起こす訳です。
住宅地を道路の反対に作っておけばこの事態は避けられたでしょう。

こうなるともう道路の車線を増やすのは焼け石に水というか、逆効果でしかありません。
この場合はもうどうしようもありませんので、交差点を工夫したり右折の道路を追加して乗り切りました。

バイパスはよいアイデアですが、ちゃんとバイパスを作って活用するにはシムシティの運転アルゴリズムをある程度理解する必要があります。
この記事は大変素晴らしいので、ぜひ読んでください。

大変だけど、路面電車も通せるようにレール付きの道路も一本作りましたよ。
しかし、車の絶対量が大きく減ったわけではありません。
ある道路が流れれば別の道路が詰まるわけで——。



しかも今度は前より重症です。大きな施設の前はしばしば渋滞するのでしょうがないと思っていたら……これは酷い。

いかがでしょうか。
この素晴らしいゲームの魅力が少しでも伝わりましたら幸いです。
シムシティ 2013 は EA Origins で 6800 円~ です(ダウンロード版)。
プレイにはインターネット接続環境が必須です。
1ライセンスで複数のPC/Macで遊べます。ただし同時に遊ぶことはできません。

2013年4月7日日曜日

楽なADVANCE曲 (追記有り)

「Basic だと暇だけど、 Advance にするといきなり手数が増えてクリアできなくなるんだよねー」(東京・40歳・男性)
BRIGHT STREAM (Advance) を30秒で閉店させた後、ある男はそう言った——。


いやもう仰る通りで。
そうなんっすよ。
BRIGHT STREAM は結構ハット叩きっぱなしだし、イントロからしてバスとずらしてハットとスネア叩くところがあり、ハットクローズも登場するということで Adv の中でも難しいほうだとは思います。
Basic が Lv 1.8 とかで Advance がいきなり 4.xx そして Extream が 7.xx みたいな曲、結構あるじゃないですか。

そんなわけで Adv でもそんな難しくない曲を探していきましょう!
ハットの刻みが少ないことを基準に、ハットとスネアの同時打ちが少ないものを選んでいます。
(間違ってもそんな勘違いはしないと思いますが、僕は半分もクリアしてませんので飽くまで知ってる範囲で、の話ですよ)

春の歌

後半のサビでハットとスネアを交互にタカタンタンタカとリズミカルに叩くところがありそこだけちょっと難しいです。
それ以外はスネアを混ぜたハット四つ打ち、ダーダッ ダーとベースに合わせてバスドラを踏み、簡単なオカズといった簡単な構成でお勧め。

愛のために

Adv に限らず Ext まで全部簡単です。
ただタム中心ですので、ハットの刻みとバスドラを軸にしたロックドラムばっかり叩いてる人は注意しましょう。

君の知らない物語

細かいことは忘れました。
でも簡単だった……はず。

DIAMOND

これは正直、原曲を知ってればこの独特なリズムパターンも簡単に叩けると思います。
でも最初は絶対戸惑う。
ハットの刻みが少ないしバスもそう沢山踏まないので、慣れたらきっと簡単な部類です。

創聖のアクエリオン

エンディングをきれいにキメるのはそれなりに難しい気がしますが、基本は簡単だと思います。

ビーナスとジーザス

これもDIAMOND同様、少々独特ながら原曲を知っていれば楽というパターンです。
シンバルとの同時打ちはありますが、判り易いタイミングです。
バスとスネアの組み合わせはちょっと急がしめに見えます。バスはツーバスで踏んだほうが色々と楽だと思います。
でもクリアするのは楽だけどフルコンボするのはそれなりに難しい気がします。

HIT IN THE USA

BECK のテーマ曲です。
ハットが少ないのでリスティングしておきます。
サビで右シンバルを軸にリズムを刻むところがありますが、それ以外は音に合わせて叩けばよくリズムも譜面もシンプルです。

以上、7曲をリストアップしておきます。
刻みながらのスネアを封印すると結構限られますね。
ハットの刻みが出来ると Lv3.xx くらいまでで以下のような曲も簡単になるでしょう。

Innocent World

ハットはずっと4ビートくらいで刻み続けるだけで、慣れの問題です。
手数はずっと少ないけど罠よりは退屈感少ない気がします。
ちなみに v8 まではもっとずっと難しかったです。



イノセントワールドと大体似たような感じで簡単です。むしろもっと簡単かも。

キミマチスカイ

同上。
あんまり覚えてないけど一発フルコンボ。
たしかハットとスネアとバスしか使わなかった気がする。

chAngE

特にコメントすることもなく簡単でした。

このへんのハットに慣れたら右シンバルでリズムを取るところがある、「全力少年」「嘘」などを足がかりに Lv3 校半から Lv4 前半までの「リライト」「サムライハート」「the WORLD」などの手数少なめの曲もクリアしていけるのじゃないかなと思います。

また思い出したら追記します。

GITADORA サントラ発売&ガラケー対応

ちょっと前の話ですけど GITADORA のサントラが発売されました!
爆弾低気圧
え?
どうせお前版権曲ばっかやってるだろって?

YES!!
YES!! I LOVE 版権曲!!

だってほらすぐ無くなっちゃったりするじゃないですか。
あるうちにやっとかないと。

あと別に版権曲だけってわけじゃないですよ。
オリジナル曲もちょこちょこプレイしてます。
スキルも 2300 を超えて、レベル 5 前後の曲なら初見でクリアできるようになってきたので知らない曲にもチャレンジしてみようっていう余裕が出来てきたわけです。
(つい最近も"春の歌" (Lv. 4.90) で超苦戦したりもしてましたが)
年末くらいに V8 から初めて、思えば成長したもんです。

以下にアマゾンから拝借したセットリストを。
アスタリスク付きおよびボールドで示したものは解禁曲です。

1. VOICE *
2. こわれそうなもの *
3. 雨ノチHello
4. Speeders *
5. 俺と愛とロック *
6. Give Us Kiss! *
7. 君と同じ季節の下 *
8. Fat snail *
9. 迎撃のフォルテ
10. Jasper *
11. Summer Lady *
12. もぎたてANGEL
13. DIGITAL GIRL
14. Like&Love *
15. ドリーム・キッス
16. ROCKET MAN
17. 三毛猫ロックンロール *
18. 金輪際のエレジー *
19. Fly with me *
20. 元禄花吹雪 *
21. Red haze *
22. Mother Tree *
23. Metallic *
24. only my railgun
25. God knows...
26. 魂のルフラン
27. Cosmic Hurricane (GFDM ver.) <つぎドカ!>
28. snow prism (GFDM ver.) <つぎドカ!>
29. 恋閃繚乱 <つぎドカ!>
30. 紅焔 <つぎドカ!>
31. SPACE DRIVER (Long Version)
32. 俺と愛とロック (Long Version)
33. こわれそうなもの (LongVersion)
34. 金輪際のエレジー (Long Version)
35. Mother Tree (Long Version) 

この解禁曲の数!
サントラ売りたいのかアプリやらせたいのかどっちかにしようよ!
意外にも版権曲が入ってます。といってもアレンジバージョンのみですね。
アレンジバージョンをちゃんと聞きたいと思っていたのでこれは嬉しい配慮。

あ、そういえば今までスマホ用だった GITADORA アプリですが、フィーチャーフォン(いわゆるガラケー)にも対応したそうです(昨日くらいに)。
まぁミニゲーム以外は全部 Web アプリですからね、あれ。
ライブに参加してクリアすることができるそうです!(当たり前)