プログラミング難しい

比較と状態遷移が難しい。


年をとるごとに、自分がプログラマとして能力が高まりつつあるのを感じる。


何故、年を取ると能力が高まっていくのかというと、将来への明るい希望が減ってくると、「自分は優秀だ」という勘違いぐらいしかすがるものが無くなるからなのだった。だから、多分俺は今後も年を取るにつれて能力が高まっていくと思う。


さて、そんな優秀な俺なので、処理内容を理解している場合は、プログラムの実装時に困る、ということはほぼ無くなっているという気がする。
(まあ、実装以外の面で、処理わからんとか、アルゴリズムわからんとか、論文全く読めないとかで停止することはよくあるので、別にプログラミング能力の高まりに対応して仕事がサクサク進むというわけでもないのだけど)


しかし、それでも、やっぱりプログラム難しいと思う点が、最後に二点残った。これは、

  • 比較が < なのか <= なのか?
  • 状態遷移ちゃんと書けたのか?

の、二点である。


まず、比較のほう。

例として、文字列から文字を探して、それが終端を超えてなければ、return 1 という関数があるとする。

// start_ptr 〜 end_ptrの間で'a'の文字が入っているなら1を返す
int find_a(char *start_ptr, char *end_ptr) {
    while (*(start_ptr++) != 'a')
        ;

    if (start_ptr ??? end_ptr) {
        return 1;
    }
    return 0;
}

さて、この時、??? に入る比較は、'<'か? '<='か? という問題である。


なお、どちらとも言えない、が正解だった。end_ptr は、終端の文字を指すか、C言語の雰囲気的に(end_ptr - start_ptr で長さが出るので)終端の次の文字を指すか、が決まっていないので、回答は一意には定まらない。


この問題は、いっっっっっつも、なんか、「えーとーーどっちだっけーーー」とか考えてる気がする。自分だけで書いてるときは習慣として、end_ptrは終端の次の文字、比較は '<' と心に決めているのだが、それでも、いっつも悩んでいるし、他人が書いた処理が返してきたポインタだった場合は、さらに悩んでいる。
そして、さんざん悩んだ割に、一定確率で間違いを挿入しているという気がする。(先週もやった)


同じような問題はあちこちにあって、2D画像に対して畳み込みフィルタ入れるとき、カーネルサイズが奇数だった場合に処理してよい範囲は、いっつも悩んでいるし、時々間違えるし、さらにそれをSIMDにする時に、間違いをもう二回ぐらい追加しているという気がする。




さて、次に、状態遷移だが、これは、もう、俺は覚悟を決めた。プログラミング学習を通じて理解した重要な事実のひとつは、「状態遷移をきれいに書く方法はこの世には存在しない」ということだった。
状態遷移は、どんなに面倒でも、それをプログラムに記述する以外の方法は無いし、新しいイベント、状態を追加するときは、新しいバグを一個追加するのだ、という気持ちで臨むことが大事である。

状態遷移は、処理が最悪の場合、イベント数M、状態数Nだと、処理の数は、O(MN)になる。本来は、状態を追加するときは、全てのイベントの組みあわせと動作確認する必要があるし、イベントを追加する時も同じように状態の数だけ動作確認する必要がある。

もちろん、いくらかは構造化できるから、実際に、O(MN)になることはほぼ無いけど、今度は、そういう構造化を入れてしまうと、それが本当に、全ての状態/イベントの組み合わせを網羅できているのか?という問題が出てくる。そういうのは、コードを見てもよくわからないし、ちょろっとテストするだけではよくわからん場合が多い(そもそもテストパターン思い付くなら対応してるはずだし)

というわけで結論としては、状態遷移処理は、どうやってもバグが入るから、解析とデバッグしやすいように、状態を一個の構造体に入れて、処理を一個の関数でまとめるとわかりやすいということになった。(もちろん、実際には共通部分分けたりぐらいはするが)



このふたつの問題の共通点は、間違っていても大体の場合動いてしまうというのがある。もちろん、正しくは、境界条件探して、テスト作って、動いているかどうか検証すべきなんだけど、関数への入力の整合性とらないといけない場合とか入力データ作るの面倒だし、そんな末端の条件分岐一個のために、全体が正しく動くデータ作るのとか面倒だし…

あと、問題がプリミティブなので、一般的な方法で対応をとるのが難しく、結局一個一個チェックする以外の方法が無いというのもありますね。



というわけで、プログラミング難しいという話だった。


状態遷移難しいという話に関連して、次回、MVCとかのMVなんとかフレームワークが何故クソなのか、という話について書こうと思う。

16.7msecでできる仕事

時間と金は絶対値を見るべきだと思う。


プログラムの高速化は、真面目にやると計測の問題になる。
高速化の作業は、実際にはプログラムの処理に対して、実際にかかっている時間が妥当なのかどうかを調べて、妥当でない場合は原因を調べるという、一種のデバッグみたいな作業になる。


この作業をするとき、処理時間を絶対値で見ることが重要だと思う。


高速化ではプロファイラを使ってボトルネックを調べましょうとかあるが、あれはあくまで第一歩で、実際に修正するには、かかっている時間が妥当かどうかを知る必要がある。それを調べるには、妥当な絶対時間と、今の処理の絶対時間というのを把握しておかないといけない。

例えば、「100MBのファイルを読む時間が全体の90%を占める、IOネックなのでこれ以上早くならない」とか言われても、それが妥当かどうかはわからない。これが、「100MBのファイルを読むのに、今は5秒かかっている」とか言われると、まあ、今のOSのファイルシステム経由の連続読み書きだと、数十MB/sぐらいなので、100MBで5秒は、妥当かどうかは、調べないとわからない際どいラインだというのがわかる。つまり結局調べないとよくわからないということがわかるのだった(声に出して読みたい不自由な日本語)


僕は、定規としてわかりやすいのは、16.7msecだと思っている。


処理時間を考えるときは、常に最新ゲームのゲーム画面を思い浮かべる。


最新ゲームは、16.7msecで、4Kの画像の各ドット対してそれなりに演算量のあるシェーダを回して、オブジェクトの各頂点にもシェーダを回して、ゲーム中に多数存在する生物のAIが経路探索し、物体の剛体衝突判定を行い、背景の流体を動かして、(だましだましとはいえ)世界中の数十人がネットワークを通じてリアルタイム対戦して、ゲーム実況者が音声を入れているのである。

0.5秒かかる処理があったとする。0.5秒というと一瞬に見えるが、ゲームなら、30フレーム分処理が動いている。はたして、あなたの、その、0.5秒かかっているその処理は、最新ゲーム画面30フレーム分と比べて、同じだけの処理をしていると言えるのか、言えないのか、とか、は、あまり考えないようにしようと思った。まあみんないそがしからね。そんないちーち細かいところまで妥当かどうかとかカンガエナイホウガヨイネ。


同じような問題として、お金も絶対値で見るべきというのがある。


一億円プロジェクトだと、100万円は、1%でしかない、が、100万は大金であるというのを忘れるべきではないと思う。100万円の価値は、1兆円プロジェクトでも、100万円プロジェクトでも、同じ価値だ。常に、100万円あったら何ができるか、というのを考えるべきだと思う。100万あれば、人間ひとり1ヶ月動かせるのである…


と、でも思ったか!??


たかが、100万で、人間を自由に動かせるという考えが甘いブラックだということに気付くべきでは??1ヶ月は30日であって、16.67msec 1フレだとすると、1ヶ月は、155520000 フレーム分の処理である。1人月(ピー)万と言うが、その仕事は、バッフィー 155520000 フレーム分の価値があるのか? 155520000 回分、4Kシェーッダーーーが動いてるんだぞ???そもそも、よく考えろ、俺が、人間が、意思を持った一個の生物が、金で自由に動かせると思ったら大間違いだぞ!!こんな人権を現金に変換する仕事なんてやめてやる!!!俺は!自由に!なるんだ!!!!


まとめ: 一般に、無職のほうが追い詰められて自由度は低くなることが多いので仕事やめて自由になろうという考えは危険です。