「プログラミングの常識」を時々見直す必要性について
自分の中のプログラミングの常識というものは、ときどき現実のハードウェアに合わせて調節しないといけない。ハードウェアが進歩し続けているので、コンピュータで簡単にできることと相対的に難しいことのバランスが変化し続けているからだ。ここでは特にストレージにフォーカスして書こうと思う。
昔はメモリが相対的にとても貴重な資源だったので多くのプログラマがメモリを節約することに血道を上げていた。例えばWindowsの初期の頃に設計されたデータ構造には、メモリをバイト単位ででもいいから節約したいという意図の痕跡がいまでも多く見受けられる。DRAMの次に速い記憶装置はHDDだったので、メモリが足りなくなればHDDにデータを保存せざるを得ないのだが、DRAMとHDDのランダムアクセスの速度差は、机の上の本の開いているページを見るのと、その本をAmazonで注文して到着するのを待つのと同じくらいのスケールで違うので、なんとしてでもデータはメインメモリに載せる必要があり、そしてメインメモリはとても少なかったので、メモリを節約することはとても重要だった。
いまではメインメモリはかなり潤沢な資源になった。数GiB~数十GiBくらいのデータなら普通のPCでも一気にメモリに読み込んで簡単に処理することができる。たいしたことのない量のデータなのにディスクベースのやり方で不必要に苦労しているケースがいまでも多いような気がするが(一気にメモリに読めばいいのにわざわざデータベースを使っているとか)、メモリをたくさん使えば簡単にできる方法があるなら、現代ではそれを選ぶほうがよいケースが多くなった。
SSDというHDDよりずっとランダムアクセスが速いストレージがストレージ階層に加わったのも大きな変化をもたらした。HDDがAmazonに本を注文するくらいの遅さだとしたら、SSDは隣の部屋の本棚から本を探してくるくらいのスケールで速いので、ファイルアクセスの局所性を以前ほど気にしなくてもよくなった。キャッシュに乗っていないファイルにどんどんランダムアクセスしてもそれほどは遅くはないのだ。そうなると、メモリに乗り切るサイズにデータを収めないと遅くて仕方がないというわけではないので、前よりもずっと大きなデータを扱っても平気になった。たとえば数100ミリ秒で結果を返さないといけないサーバの場合、HDDなら数十回シークすればすべての時間を使い果たしてしまうけど、SSDならそれくらいは全然平気だ。これはそもそもどういう処理が実現可能かというプランニングに大きな影響を与えることになった。
一方でDRAMはCPUに比べればすごく遅くなった。CPUが恐ろしく速くなったとも言えるのだが、メインメモリにアクセスするには数百クロックを必要とするようになり、そのレイテンシを隠蔽するためにL1, L2, L3キャッシュがCPUの近くに加わった。L1キャッシュとメインメモリは、それこそ「覚えている」のと「手元の本の開いているページに書いてある」くらいに違うので、現代のプロセッサはメモリの局所性が悪いプログラムに対しては本来の性能が全然発揮できない。この変化によって、配列など局所性のよいデータ構造を使うアルゴリズムが、ランダムアクセスを必要とするアルゴリズムよりかなり有利になった。たとえばJavaでArrayListを使うほうがLinkedListを使うよりシーケンシャルアクセスでも大抵ずっと速いのはこのためだ。多くの場合、凝ったアルゴリズムは局所性が悪いので、理論的には速い凝ったアルゴリズムを考案してみても、配列などを使った単純だが効率の悪そうなアルゴリズムに勝つことは意外と難しい。大規模なデータを扱うのならこういうこともある程度頭に入れてデータ構造を設計しないといけない。
これからもストレージ階層は変化しつづけるようだ。コンピュータを構成するコンポーネントの相対的な速度差は変わり続けている。それに、あまり話題になっていないみたいだけど、Intelの3D XpointメモリはちょうどDRAMとフラッシュメモリの中間くらいの速度と値段なので、こういったものが広く使われるようになれば、SSDの普及以来初めてまた新たなストレージレイヤが加わることになる。こういったものもまたプログラミングにおけるゲームバランスを変化させることになるはずだ。
というわけで、昔の常識は今の常識ではないし、今の常識は未来の常識でもない。常にどういうバランスでプログラムを書くのがよいのか自分の中で調整していくことが大切だ。