ページへ戻る
+ Links
印刷
OT/0001
::
OSASK計画
osaskwiki
:
OT
/0001
ページ内コンテンツ
小さいこと・速いことをどれだけ重視するか
0.重要なこと
1.サイズや速さをどう評価するか
2.人間本位な考えを捨ててみる
3.時代に依存しないプログラミング
4.機能の重要性
5.コンパイラの最適化との関係(追記)
こめんと欄
小さいこと・速いことをどれだけ重視するか
OsaTech
より
(by K, 2004.09.05)
0.重要なこと
以下に書いてあることは「よいこと」ではありません。速くて小さいプログラムを作るために役立つ思想が書いてあるだけであって、これはただの技術なのです。何が正しいのかとか、ここに書いてあることは正しいのかどうかではなくて、
開発をしているときだけこういう気分になりさえすれば
、小さくて速いプログラムが誰でも作れるようになりますよ、とそれだけのことなのです。小さくて速いことよりも、他のことが重要なことは十分にありえます(仕事の上ではまず納期が一番重要、なんてことはあるでしょう。・・・そんなときはどんな犠牲を払っても納期が優先です。納期に間に合うと分かってから納期に遅れない範囲で以下の努力をするのはとてもすばらしいです)。それをはき違えてはいけません。
以下はもっとも効果のある「技術」であり、たとえばWindowsプログラミングの範囲内であっても劇的に改善するでしょう(つまりは僕の作るwin32アプリが小さい秘訣は、これなのかな?)。これによってえられる効果(ソフトウェアの価値の向上)は10倍以上になることもあるでしょう。
もし新しいことを始める場合などは、以下の観点を多少は意識してもいいと思いますが、あまりべったりなのはよくないかもしれません。最初は試行錯誤するものですから、たくさんのコードを書いてたくさんのコードを捨てるのは避けられないでしょう。捨てるコードに時間をかけて最適化しても、それは最適化の練習くらいにしかなりませんので。むしろ、何をするべきかがしっかり決まっているときや、リファクタリングするときなどには有用な視点だと思います。
1.サイズや速さをどう評価するか
非常に単純です。機能を損なうことなくサイズが半分になれば機能密度は倍になるので、サイズが半分にできたらこのプログラムは倍の価値になったと解釈します。それくらいサイズを価値あるものと認識するべきです。もちろん倍の価値であれば、値段が2倍になってもいいのです。開発時間が2倍になってもいいのです。僕はそういう価値観でOSASKに到達しました。
速さも同様です。機能やサイズを損なうことなく速度が2倍になれば、それだけCPUタイムを節約できているのですから、時間に対する機能密度が倍になります。だからこのプログラムは倍の価値になったと解釈します。
これ以上速くしても(実用上の)意味がないとか、これ以上小さくしても(実用上の)意味がない、ということは大いにありえます。それでも、改良の余地があるなら改良するべきです。もちろん、今すぐに改良に着手する必要はありません。実用上の意味があるところを優先して、そのあとでも十分でしょう。
大事なことは、たとえもはや実用上の意味がなくなるくらいの段階に達していても、速さが倍になればやはり価値は2倍になり、サイズが半分になれば価値が2倍になる、という価値観を持ちつづけることです。この気分を守るだけで(=それ以外に何もしなくても、そしていかなるプログラミング言語を使っていても)、かなりOSASK的なコードに自然に到達するでしょう。
なお、プログラマも人間ですから、やりがいがないとあきます。やりがいを維持するためにも、高速化の際には80%-20%ルールを意識しましょう。これで少ない労力で効率よくスピードアップできます。
ついでにここでサイズ最適化のためのルールを簡単に説明しておきます。80%-20%ルールによれば、コードの20%が実行時間の80%に影響しているわけで、つまりコードの80%は速度が落ちてもほとんど影響がないということです。だからそのような80%に対してはサイズ優先で最適化します。サイズ優先部分では、高速なアルゴリズムなんて不要です。単純な(いわゆる馬鹿にされているような)アルゴリズムが重宝します。
2.人間本位な考えを捨ててみる
今までいろいろなプログラミング手法が提唱されてきましたが、それは常に正しいことでも正義でもなんでもなく、その多くは基本的にはプログラマが楽をするためのものでしかないということを認識しなければいけません。しかし少々強引なことを言えば、本来プログラムは人間が読むために書くのではなく、機械が読むために書いているのです。だからまずは機械にとっての都合を優先してみてはどうでしょうか。この視点で今まで見えなかったものが見えてきます。
たとえば汎用的なライブラリは非常に便利ですし、オブジェクト指向はすばらしいです。しかし汎用的なライブラリは使わない機能を多く持つ傾向がありますし、オブジェクト指向においても使わないインターフェースを大量に持ったものを知らずに使っている場合があります。
これをもっと機械本位に考えてみます。サイズが小さく処理が少なくて済み、さらに必要な機能をだけを的確に提供するようなライブラリ構成やクラス構造がよいのです。またソースの読みやすさなんて機械にとってはどうでもいいことです。実は人間にも読みやすくて機械にもうれしいようなソースの書き方が存在します。たまにこれが両立できない場合もありますが、そのときに優先するべきは機械の都合であって、人間の都合ではないのです。
JavaをやめてCに切り替えてみる、なんてことだけでも機械の都合に近づいているのでこの観点ではいいわけです。
移植性も当然人間の都合です。機械は移植性が高くなってもうれしくありません。もちろん、移植性を高める過程でよいコードに到達することはあります。それは機械にもとても嬉しいです。
もちろんプログラマは自分の能力をわきまえなければいけません。機械本位にしたらバグがたくさん出て直せなくなった、なっていうのは本末転倒です。手におえないバグがでないくらいまででこの方向への努力は打ち切るべきです。バグが取れないプログラムは、どんなに速くてもどんなに小さくても、それはただのゴミです。
ユーザインターフェースも同じです(これはクラスライブラリのインタフェースも含む)。今はとかく人間にとって理解しやすいインターフェースを考えてばかりいますが、僕の考える素晴らしいデザインとは、機械本位のインタフェースを人間にとってもわかりやすい形にまとめあげたものであって、人間のワガママに機械が苦しめられるようなものではありません。機械を効率よく動かしたいのなら、機械の負担が少なくなるようなデザインを選ぶべきです。そして往々にして、むしろそういうデザインが最後は人間の感性にもマッチしたりします。
非常に細かいレベルでは、たとえば配列の添え字は1からか0からかといえば、0からのほうが機械に都合がいいわけです(これは数学的な伝統には沿わないので、人間の都合を考えれば1からということになる)。そしてそういう記法のほうが結局は簡素なコードを書きやすかったりしますし(たとえば等差数列の一般項の式が、a[n]=a+d*(n-1)ではなく、a[n]=a+d*nになったりする)、処理系の最適化の負担も小さくて済んだりします。これを互換性の放棄の一種としてみなすこともできるでしょう(数学的表現との互換性が放棄されている)。
機械本位の考え方に慣れると、世間の一般慣習にとらわれずに問題を単純に把握できるようになり、結局はより深く問題を考えられるようになります(ここでいう問題はもちろんプログラミング上の問題のこと)。それで他の人よりもよいアルゴリズムに到達できるようになります。
僕の個人的な考えとしては、プログラミングというのは、与えられた機械で問題を解くという数学的な問題で、それは究極的には人間が自分たちの便利のために考えたルールや記法や命名規則や階層構造などには依存せず、その問題に応じたベストのルールや記法や命名規則や階層構造があるはずです。そのときの社会的背景やプログラミングスタイルの流行などによって、解が変わるなんてことはありえません。だから、そういうことは一度忘れて、まずは機械を中心に考えるわけです。
3.時代に依存しないプログラミング
今はCPUが速いからとか、今はメモリがたくさんあるから、みたいな理由でベストの解は変化しません。メモリをたくさん使うけどCPUの負荷は小さいとか、CPU時間を多く使うけどメモリの負荷は小さい、みたいなのはありえます。どっちがベストなのかは状況によりますが、どちらも重要な解でしょう。どちらか一方で済むとは思えません(だからtek1だけでいいとかtek5だけで十分であるという発想は僕にはありえない)。
もしあなたがプログラムの改良を「今は~」的な理由や「これからは○○だから」的な理由でやめたのなら、それはそういうくだらないものであって、OSASKがいうところのベストではありません。たぶん時代が変わるとまた書き直すことになるんでしょう。
今はCPUが速いからこれでも大丈夫、とかいってリリースされたライブラリは、結局そのアルゴリズムを酷使しなければいけないアプリからはボトルネックでしかなく、そのためにそのコードが書き直されることになります。ベストなコードが一個あればいいのに、中途半端なコードが大量生産される結果になります。メモリについても同じです。今はメモリが安いから大丈夫とか言っていると、そのルーチンをたくさん使おうとするときにメモリ不足のせいで書き直しをしなければいけなくなります。
また逆に、仮にそのルーチンの利用頻度が低くて重要でないとしたら、そんなものがたくさんCPU時間を浪費したり、メモリを浪費したりしても許せるものでしょうか。主役でもないのに主役以上にリソースを食って主役の計算に支障が出ているかもしれません。
ということで、時代に依存したプログラムは結局その場限りのものであって、多くの人に使われてはいきません(まあそれでも無理にそういうものが普及した結果がWindowsやLinuxの肥大化なのかもしれません)。時代を言い訳にしなければいけないような中途半端なものを作るくらいなら、その数倍の時間をかけてでもベストなものを一つ作るほうがずっとマシです。
4.機能の重要性
必要な機能はどんどんつけるべきです。しかし利用頻度が低い上に他の機能の組み合わせで表現できるなどという場合は、そんな機能はいらないということです。いらない機能がどんなたくさんあっても、それはソフトウェアの価値にはなりません。使いにくくてサイズが無駄に増えるだけで、かえって害にもなります。
こんな機能は機械にはつらいから本当はあったほうがいいんだけど省こう、という姿勢は行きすぎです(その論法を強烈に押し進めていいなら、0バイトの何もしないプログラムが最高の価値だということになりかねません)。その機能が人間にとって有用ならたとえプログラムのサイズや速度の低下を招いてもつけるべきです。デザインでがんばって限界に達したらそれでいいのです(これ以上機械本意にしたら目標の便利さに到達しない、など)。ただ処理があまりにも重いのなら、設定やオプションなどによって必要なときだけ機能するようにするとか、その機能がないサブセット版も用意しておくとかの、そんな工夫はあったほうがいいかもしれません。
本質的にどうしても速くできない処理は存在します。そういうものは、今のCPUをもってしても高速にはできないようなものであっても、そのようにプログラミングするしかないのです。それはプログラマのせいではありませんし、実用レベルではなくてもプログラムとしてはベストなのです。これは浪費ではないので全く問題ありません。速くできるのに速くしないのはおかしいですし、小さくできるのに小さくしないのはおかしいですが、もうこれ以上速くできないものや小さくできないものは、たとえどんなに遅くて大きくても、素晴らしいプログラムです。
5.コンパイラの最適化との関係(追記)
もしコンパイラが十分に優秀になれば、人間はこんな苦労をする必要は全くないです。これは非常に正論であり、僕も大いに賛成であります。だからコンパイラをかしこくしようという試みは大賛成です。また、「この程度なら将来はコンパイラの最適化でカバーできるはずだから」という理由であえてプログラムを人間本位にしておくのも理にかなっていると思います。
僕としては、「最近はCPUが速いから人間は無駄をしてもいい」みたいな発想は(性能向上を目指すプログラミングのためには)有害でしかない、といいたいのです。コンパイラの最適化が優秀だから(もしくは優秀になる予定だから)人間はそこまで機械本位になる必要はない、ということであれば問題はないと思います。
ついでに言うと、僕は基本的に現在実現している最適化しかあてにしませんが、これは単に僕が「最適化不信」に陥っているだけな気もするので、これは有益ではないと思います。どのくらい最適化をあてにしていいのかどうかは僕の知識ではなんともいえませんが、とにかくあてにするのは悪くないと思います。
いやむしろ、あてにするべきだといってもいいかもしれません。機械にできることをわざわざ苦労して人間がやるなんて、ただのコストの浪費でしかありません。しかし道具の進歩でも克服できないのなら(そんなレベルの最適化がそもそも存在するのかどうかも分かりませんが)、それは人間がやるしかありません。将来の最適化技術をあてにする場合、あるレベルの最適化が理論的に可能だとしても、その最適化コンパイラが出るまでに10年掛かるなら、10年間はその非効率プログラムで我慢することになるので、それで問題がないかを検討する必要はあるでしょう。
こめんと欄
Last-modified: 2009-11-21 (土) 00:00:00 (JST) (319d) by k-tan