マルチコア時代のスケーラビリティ

主に発熱の問題から、CPUの進化はコアの高性能化の方向性をぐっと減らし、マルチコア化によって、ムーアの法則に則って増え続けるトランジスタを消費する方向に進むことになりました。id:hyoshiokさんが、http://d.hatena.ne.jp/hyoshiok/20060430#p1で指摘されている通り、従来は、アプリケーションを開発する場合、プロセッサ性能やメモリ搭載量は、将来増加することを織り込んで、設計することが正しい戦略であったわけです。

一般にCPUやメモリ、HDDなど、ハードウェアリソースの追加に比例して、性能が向上することをスケーラビリティと呼びます。ソフトウェア開発者にとって、スケーラビリティがあるソフトウェアを設計/実装することが求められてきましたし、これからも求められていくでしょう。実際、高いお金を払って、CPUを2個から4個に増やしたのに、性能は2個のときと変わらないとか、逆に悪くなるということがあれば、ユーザは怒るでしょう。CPU2個のマシンと4個搭載できるマシンでは値段は、1桁違ったりしますし。

しかし、スケーラビリティのあるソフトウェアを開発するというのは、じつは大変難しいのです。例えば、ワーキングセットを下回る様なメモリしか搭載されていなければ、論外なのですが、ワーキングセットに対して十分なメモリが搭載されている状態で、メモリを追加して性能が伸びるというのは、非常に限られたアプリケーションだけです。それどころか、OS/アプリの実装によっては、逆に性能が下がる場合すらあります。

これに対し、CPUはいままで非常にありがたい進化を続けてくれたのです。つまり、アプリケーション側は何の工夫もしなくても、CPUの性能向上の恩恵をそのまま受けることができたわけですから。しかし、今後のCPUは、コア単体の性能向上を望むことはほとんどできなくなります。代わりに、コアの数が増えるわけです。ですからこれからの時代、コアの数に対するスケーラビリティのあるソフトウェアを開発することが求められるわけですが、私はこれに関してあまり楽観的ではありません。

いろいろと論文を漁ると、CPUのスケーラビリティに関する物がでてきますが、私が見た範囲でスケーラビリティが一番あるOSでも、16コアくらいで性能向上が頭打ちになっています。それもかなりマルチプロセッサ環境に向いたベンチマークを利用した場合にです。一般的には、4〜8コアくらいが限界だろうと思います。それも、コア数に対する性能の比例係数は、1ということはなく、0.1〜0.6といったところがが良いところではないでしょうか。もちろん、CPUのコア数に関するスケーラビリティは、アプリケーションに非常に強く依存するので、コア数に比例した性能がでるアプリケーションを否定する物ではありませんが、世の中の多くのアプリはそれほどCPUに関するスケーラビリティが無いのではないかと思っています。

さらに問題なのは、CPUスケーラビリティの高いアプリケーションを開発するというのは、非常に難しいのです。性能を向上させるためには、最低でもコア数を超えるThreadを起こして、それをぐりぐり回すようなコーディングをしなければなりませんが、Multi Threadプログラムがバグったときのデバッグは、時に筆舌に尽くしがたい苦難があります。私が無能なだけかもしれませんが。

Linuxでも、CPUスケーラビリティの向上については、2.5時代から様々な取り組みが行われていますが、基本的には(1)lockless設計、(2)lockの細分化、(3)軽量なlockの利用ということに尽きます。(1)については、例えば、slab allocatorは、CPU毎にslab pageのリストを持つことで、allocationのためのlockの削減を行いました。CPU毎にリストを持つことは、メモリの無駄になるわけですが、メモリの無駄を承知でlockless実装にこだわったわけです。(2)については、一番有名なのはBKL(Big Kernel Lock)使用箇所の削減があります。(3)については、例えばSemaphoreをRCU(Read Copy Update)に書き換えることで、性能向上を果たした箇所が多数あります。

CPUスケーラビリティ向上の難しさの実例:MySQLの実装に関して

アプリケーションの性能向上を目指した実装の例として、MySQLについて取り上げます。MySQLソースコードを丹念に読んだわけではありませんので、誤解がありましたら、ご指摘ください。

先月の話ですが、id:hyoshiokさんのMySQLの性能に関するエントリhttp://d.hatena.ne.jp/hyoshiok/20060301#p1で、コメントさせていただきました。

MySQLのmutexは、空回りをして待ちながらmutexの取得に何回かトライし、だめなら一旦あきらめてyeildする実装になっていました。しかし、この実装ではCPUスケーラビリティが低いと思います。予想されるlockの保持時間に合わせて、lockの保持時間がきわめて短い事が確実なlock→spinlock、比較的長時間保持されるlock→トライしてだめならすぐ寝るmutexと使い分けるような実装にする必要があります。また、可能であればthreadの優先度を制御して、lockを保持しているthreadの優先度を上げるべきでしょう。

余談:HyperThreading環境で性能が低下する件について

件のエントリでは、puase命令を追加しても性能は変わらないが、Hyper Threadingを無効にすると、性能が向上するという結論が示されていました。これは、MySQLのCPUスケーラビリティの無さを示している可能性が高いと予想しています。

ハード構成は16CPUだそうですが、仮に2GHzのマシンだったと仮定した場合、HyperThreadingを有効にすると仮想的に1GHz,32CPUのマシンに見えます。TyperThreadingの場合、CPUストールの際にsiblingが実行されるはずなので、実際にはもうちょっと性能が良いとして、1.2GHz,32CPUのマシンだったとしても、2GHz,16CPUのマシンと勝負して勝てるのは、よほどSMP性能に気を使ったOS/アプリを利用した場合だけのように思います。どちらかというと、MySQLは健闘している方ではないかと。

更に余談:動的トレース機能について

上記、http://d.hatena.ne.jp/hyoshiok/20060430#p1に、fjの教祖様が、http://d.hatena.ne.jp/hyoshiok/20060430#c1146410003というコメントをつけてますが、JavaならAspectJとかいかがでしょうか?また、SoralisならDtrace、Linuxならsystemtapもあります。systemtapについては、feedbackいただけるとありがたいですm(_ _)m