Ryuz's tech blog

FPGAなどの技術ブログ

近代CPUのメリット/デメリットを考えてみる

はじめに

我々プログラマは CPU/GPU/NPU/FPGA なんかを特性に合わせて選択しながらプログラミングすることになっているわけですが、AI以降、CPU以外のプロセッサの台頭が著しいですので、今更ながら基本となる CPU をもう一度眺めてみようという書きながら考えているポエムです。

当然ながら私はCPUユーザーではあれど、CPU設計者ではありませんので、ユーザー視点から好き勝手レビューするという話なので、素人考察になるわけですが、マサカリ投げずにお付き合いください(笑)

CPU の進化の歴史を覗いてみる

4004 からのCPU の歴史を眺めてものすごく大雑把な感想を述べると

  • 1命令の実行時間を縮めるのに腐心していた時代(CPI縮小&周波数向上)
  • 一度に実行できる命令を増やすのに腐心してきた時代(IPC拡大&コア数増加)

の2つの時代に分けられる気がします。 ここで CPI(Clock cycle per Instruction) と IPC(Instruction per Clock-cycle) はまあ逆数を取っただけではあるのですが、考え方の転換の象徴でもあるので使い分けてみました。

どちらも 時間当たりの命令実行数 を増やすことにプロセッサ設計者が腐心されてきたことには変わりはありません。

もちろん、枝分かれとして、電力とかコストとか耐久性とか別の品質軸にウェイトを振った製品もいっぱいありますが、大雑把な流れとして、「絶対性能の向上」を幹として歴史を紡いでいるかと思います。

歴史の前半では、1命令を何クロックサイクルも掛けて計算していましたので、これを 如何に短くするか が重要で、CPIを下げることと、周波数を上げることが重要だったように思います。 (このあたりで1命令で出来ること自体を複雑にするかシンプルにするかでCISC/RISC論争とかがあった気もしますが、今となってはあまり本質ではないのでおいておきます(苦笑))

実際、当初は1命令に何十サイクルも掛かっていた命令実行は、最終的にはスーパーパイプライン技術の上で、実質1サイクルで実行できるようになり、周波数も数MHzだったものが数GHzまで達しました(半導体の微細化と、パイプライン段数を深くして1つのステージをどんどん薄っぺらくした賜物)。

一方で、このあたりで、半導体バイスとしても熱問題などで周波数の向上が頭打ちになり始めてきました。

そうなるとここからは、1クロックサイクルの中で 如何に沢山の命令を実行するか と言う話になっていき、

  • スーパースカラによる同時複数命令実行
  • OoO (Out of Order) による命令依存関係制約軽減による並列化増進
  • SMT (Simultaneous Multi-Threading)による命令依存関係制約軽減による並列化増進
  • マルチコアによる並列実行

などの方向に向かったように思います。 なお、ここでは SIMD(Single Instruction, Multiple Data) は、少し本質から外れるので話を置いておきます。

以前からポラックの法則というのがありましたが、もともと直列に書かれていたプログラムを互換性を保ったまま並列実行しようとしているのが現在なので、ますますリソース当たりの性能向上のハードルが上がっているようには思います。 いずれにせよLSIトランジスタ数はある程度ムーアの法則で増えているのに、CPUの絶対性能はそれより低いオーダーでしか伸びず、言い換えると非効率さが増加気味になってきているわけです。

なぜCPUが重要なのか

そんなCPUを裏目に、昨今、「CPU の何百倍の性能を誇る GPU で AI が加速した」みたいな話はニュースなどで誰もが聞くようになってきました。

「じゃあもうCPUなんてやめて GPU だけ載ったパソコンやスマホを作って、WindowsLinuxmacOSGPU 上で爆速で動かせばいいじゃないか、ブラウザも、表計算も、GPUでもっとサクサク動いてくれ!」と思われても仕方ありません。

が、残念ながらそうはいかず、CPU + GPU 構成のパソコンや、GPU入りCPUチップなどでしかシステムは構築できず、依然 CPU はコンピュータの心臓部です。

これは言うまでもなく、CPU で実行するのが最も効率が良いプログラム というものが大量にあるからに他なりません。 一応GPUチューリング完全なプロセッサですのでやろうと思えば何でもできるはずですが、 一度に同じ計算を大量にできる というだけで、それ以外はからっきしです。

では CPU が無いと困る処理とは何なのかを改めて考えてみると

  • 順番にしか計算出来ないものが1つづつやってくる処理
  • バイナリレベルでの高い互換性が必要な処理

の2つを真っ先に思いつきました。

前者は そもそも人間がそうである ため、世の中に大量にあふれていて、例えば表計算ソフトの複数のセルに一度に数字を入力したいという人はあまりいません。例えばマウスカーソルが1000個表示されていて全部別々に操作して入力できる達人がいれば 1000コアのGPU は活躍するかもしれません。ですが残念ながら多くの人間は1つづつしか処理できません。世の中にはこういう処理が溢れており、「1000個いっぺんに同じ処理がしたい」ほうが本来は稀なのです。

幸い、ビデオ表示処理には、「ディスプレイのピクセル数だけ同じ処理がしたい」という需要があったため、古くからGPUは並列演算の効率化が行われており、3D-CG の為にいろいろやってたところ「大量の積和演算が同時にしたい」というAIの需要と たまたまマッチした 為、今に至るわけです。

また、バイナリ互換の話についても、いろいろなインタプリタ言語や仮想マシンの技術が進歩しても、最後は、移植性のある基盤ソフトが無いとそもそもベアメタルからのパソコンのセットアップがままならないわけで、そもそもBIOSが作れてOSを作れるという前提を作ってくれてるのがこの「バイナリ互換」というものだったりします。 こんなものが何百種類があっても正直困るわけで、群雄割拠な時代もありましたが、なんだかんだで最近は x86 と arm と RISC-V の3つにかなり集約されてきている気がします。おかげでエコシステムも安定してきていますし、これは非常に苦労しつつもバイナリ互換性を保ってくださっているCPU開発者の方々の苦労の賜物なのかと思います。

進化の難易度が年々上がるCPU

そうはいっても年々CPUが得意としている分野の性能向上は苦しくなってきているように思います。

現在のハイエンドCPUの IPC は 10 を超えていたりするわけですが、なまじ汎用レジスタアーキテクチャなんかだったりすると、全部の処理ユニットが全部のレジスタにアクセス可能でなければならず、また処理結果を全部同時に書き戻せないといけません。次のサイクルには次の処理結果がまた IPC の数だけ同時にやってくるのです。 以前にこんな記事を書きましたが、対角線の数だけ規模が爆発していきます。 汎用レジスタやめて専用レジスタに戻すべきじゃないかとすら思ってしまいますが(素人考え)。

OoO の仕組みもより広い範囲で命令順序を入れ替えるためにどんどん複雑化しています。 リオーダーバッファはどんどん深くなり、後で辻褄を合わせるのも大変ですし、後から例外でも発生しようものならそこまで矛盾なく巻き戻せないといけません。

CPUがもっとも得意なのが逐次スカラ処理であり、スカラプロセッサと互換性を持ったまま高性能化しようとしているところにやはり限界を感じます。

しかしながら、そういう処理はそもそも人間というモデルがそうであり、人間の作ったルールを処理したり、人間とコミュニケーションをとる以上永久に続く問題な気がします。

人間が書いた自然言語の小説が10万文字あったとして、伏線や起承転結のある物語の10万文字を同時に読み込んで1サイクルで理解して伏線回収に感動するようなことは、人にも機械にも無理なのは、自然言語がそうなっている以上仕方のないことだと思います。

なお、そういった中でCPUも SIMD などのCPU以外が得意なことも取り込んで性能改善を行っています。 これはもちろんCPUの応用範囲を広げる大変有効なことなのですが、もともとCPUが得意だった逐次スカラ処理分野の高速化には寄与しないあたりが悩ましかったりします。

どのようにプロセッサを使い分けるべきなのか?

御託が続きましたが、我々プログラマは CPU/GPU/NPU/FPGA などのプロセッサをどう使い分けるべきなのでしょうか?

大雑把に言うと、専用ハードウェア(NPUなど)に嵌る処理はまずそこに当てはめ、並列化の余地のない逐次処理はCPU、同じ計算を大量に行う場合はGPU、という事になってくると思います。 最後に、並列化の余地はあるが異なる演算を大量に行い、なおかつ専用ハードウェア化できないぐらい変化が速いか需要が少ない部分が FPGA などに残ることになります。

私はよく Zynq-MP を使うので以前にこんな記事を書いていたりするのですが、Zynq-MP は Linux などの動くアプリケーションプロセッサと、Real-Time OS などが動かせる リアルタイムプロセッサと、FPGA部分と、GPU まで搭載しているなんでもありプロセッサです。 後継の Versal に至っては、XDNA というNPU まで入っており、ほんとに全部乗せ SoC になっています。

私はなるべく、システム全体の処理を、それぞれ得意なプロセッサに割り当てることを心がけています。

そういった中でアルゴリズムが固定されているケースもしばしあるのですが、そういう場合は「そのアルゴリズムがどのプロセッサ向けに作られたか?」でかなり決まってしまうように思います。

一方で、似たような結果が得られるならアルゴリズムごと新規に作ってよい というケースであれば、どのハードウェア向けにアルゴリズムを組むかでかなり話が変わってくるように思います。 GPUがあるならなるべく同じ計算の並列実行が生きてくるアルゴリズムを捻りだそうと悩みますし、特殊な演算器を持ったCPUなどがあればそれを使おうしますし、キャッシュサイズやデータ帯域なんかも考えながらアルゴリズムに手を入れていきます。FPGAをターゲットにしてよいならアルゴリズムの工夫範囲は一気に広がり、かなりいろいろな方法を候補に上げながら落としどころを探すことができます。

案外どのプロセッサでも、それ向けにアルゴリズムを考え直すと、いろんなプロセッサでそこそこ性能が出たりもします。

どんな時にFPGAなのか?

CPUの話をしておいて何なのですが、まあ、当ブログでFPGA に触れないわけが無いので最後に少し触れます。

正直、単純なデータプロセッシングの性能のみで FPGA を選ぶケースはかなり稀だと思います。

  • CPUやGPUに無いような特殊なデータ型での演算を、ヘテロジニアスに大量に行う
  • パイプライン演算がやりやすく、ストリーム処理が行いやすい

のようなケースでしょうか?

一方で、IoT となると話が変わってきます。FPGAプログラマブルでありながら

  • 現実世界とのインターフェースであるI/Oロジックがプログラムできる
  • リアルタイム性が高く、クロック位相などピコ秒単位で調整できることもある

などの特徴があります。

ようするにセンサーやアクチュエータを介して、現実世界とインタラクションできます。

こちらから何か作用すると、現実世界は変化しますので、それをまた取り込むという事を、非常に高いリアルタイム保証の中で組めます。 要するにアルゴリズムのデータループの中に「現実世界」を組み入れてしまう事が出来るわけです。

それこそピタゴラスイッチみたいなことを想像してもらっても良いかもしれません。 このような実世界を巻き込んだアルゴリズムはCPUやGPUではそもそも実行自体が不可能なものが多数ありますので、こういう分野をシステムの一部にでも保有すると計算機システムの面白みが一気に増大していくわけです。

おわりに

今回は、最初に8割ぐらい書いたところで、割り込み作業が入ってしばらく放置していて、最後の部分だけ慌てて書いたこともあってかなり、ちぐはぐなことを書いた気もしますが、いつものポエムという事でご容赦ください。