眼鏡止水

FPGA、ネットワーク機器、料理、そしてメガネ女子。

【SMP】作ろう、マルチコアCPU ~(1) 背景と課題~

あなたの自作CPU、マルチコア化しませんか

CPUの実装にフォーカスした日本語の入門書が増えたことで、自作CPUの敷居が下がっています*1*2*3。 また、自作CPUに関するウェブサイトやOSSも多く、多くの人の理解を助けていると思えます。 特に、Linuxを起動できる本格的なシステムの実装例も増えてきています。

しかしながら、ことマルチコアについては、実装の詳細を解説する書籍やウェブサイトはほとんどありません。 さらに惜しいことに、大学の専門課程においても、必ずしも十分な知識と経験を提供できているとは言えません。 講義時間に限りがあるとはいえ、残念なことです。 並列コンピュータに関するある書籍は、現状と課題をさらに明確に述べています。

もちろん、CPU、メモリ、キャッシュなどのコンピュータの本当の基本はわかっている必要があるが、パイプライン、複数命令同時発行、動的スケジューリング、分岐予測、投機実行などの技術は、並列コンピュータを理解する上でほとんど関係ない。現在の多くの学生にとって、シングルプロセッサのアーキテクチャの詳細を学ぶよりも、本書で紹介する並列コンピュータを理解するほうが重要である。

www.ohmsha.co.jp

情報の少なさのせいか、「マルチコア化はなんとなく難しそう」という声がしばしば聞かれます。 しかし実は、あなたの自作CPUをマルチコア化するために超えるべき山はさほど多くありません。 マルチコアに興味はあるけれど一歩が踏み出せないというあなたのために、筆者がRISC-Vプロセッサをマルチコア化した際の知見を連載しようと思います。

マルチコアの基本構成

一口に「マルチコア」といっても、いろいろな構成が取れます。 ここでは、メモリアーキテクチャに基づくマルチコアの分類を紹介します。 この分類方法は、以下の観点を用います。

  • 物理的なメモリは集中か、分散か
  • 論理的なメモリ (空間) は共有か、非共有か

自作CPUのマルチコア化の第一歩として、集中共有型マルチコアの実装を目指すことをお勧めします。 その次のステップとして、分散共有型に取り組むのが良いでしょう。 これらの方式は論理的なメモリを共有しており、例えば一つのOSを用いて制御できます。 自作マルチコアでnprocを実行し2とか4とかが返ってくると達成感があります。 また、メモリを共有するコアたちの役割が均質である場合、その構成はSMP (Symmetric Multiprocessing) などと呼ばれることがあります。 Linuxでuname -aと打った際に出てくるSMPとはこのことです。

非共有型はそもそもマルチコアを作ってる感があまりありません。 個人的には、ラズパイクラスタを作ったほうが学びがある気がします。

本連載は、集中共有型マルチコアの実装を目標とします。 参考程度に、集中共有型と分散共有型の解説を述べます。

集中共有型マルチコア

集中共有型マルチコアの構成例

集中共有型では、物理的なメモリが一か所に集中しています。 コアたちは相互結合網 (interconnection network) を経由したり、メモリの持つ複数のポートを活用したりします。 また、コアたちがメモリにアクセスする際の遅延や優先順位はすべて対等なことが典型的です。 この方式はUMA (Uniform Memory Access) とかCSM (Centralized Shared Memory) などと呼ばれることがあります。

この方式はシングルコアの自然な延長にあり、実装が比較的簡単です。 技術的には、例えばAXIなどのバスプロトコルをきちんと実装したコアは、単純に複製しインターコネクトのポートを増やすだけでそのまま動くことがあります*4

一方で、この方式はスケーラビリティが低いという問題があります。 コア数が増えるにつれて、メモリやインターコネクトの帯域が不足してくることがその要因の一つです。 それでもやはり、4コアとか8コア程度であればこの方式でも良いようです。 本連載は自作マルチコアの入門者向けですので、この方式を採用します。

分散共有型マルチコア

分散共有型マルチコアの構成例

分散共有型では、各コアが近傍にメモリを接続しており、高速にアクセスできます。 一方で、あるコアは相互結合網を経由して遠隔にあるメモリにアクセスできます。 その結果、あるコアがメモリにアクセスする際の遅延は、そのメモリが自身の近傍にあるか否かで変化します。 この方式はNUMA (Non-uniform Memory Access) とかDSM (Distributed Shared Memory) などと呼ばれることがあります。

コアとメモリの組をノードと呼ぶことがあります。 高性能なプロセッサの中には、一つのノードにさらに複数のコアを持つものもあります。 特に、全体としては分散共有型で、各ノードは集中共有型という構成が用いられます。

この方式の特徴として、相互結合網の設計の自由度が非常に高いという点が挙げられます。 クラスタネットワークと同様に、性能や冗長性、リソースといった観点から様々なトポロジを選択できます。

一方で、ノード間通信の実装が比較的困難です。 入門者はまず集中共有型に挑戦し、次のステップとして分散共有型に挑戦していただくことを推奨します。

参考までに、NUMAっぽいアーキテクチャを実装するOSSであるBlackParrotを紹介します。 厳密にはNUMAではないかもしれません。 参考まで。

github.com

マルチコア化の課題

自作CPUをマルチコアにする課題は、あなたのコアがすでにどれだけ高度な設計になっているかに依存します。 あなたのコアが独自のオレオレプロトコルでメモリと接続しているなら、そのプロトコルを適切に拡張する必要があるでしょう。 あなたのコアがキャッシュを装備しているなら、キャッシュコヒーレンスを維持する必要があるでしょう。 そして、コアを調停する相互結合網を設計し実装する必要があるでしょう。

やるべきことを明確に (そして簡単に) するため、本連載は以下のベースラインを想定します。

  • コアの命令セットアーキテクチャ (ISA) はRV32IAである
  • コアは1命令発行のin-orderで、ロードキュー・ストアキューなどを持たず、プリフェッチもしない
  • コアはL1命令キャッシュとライトバック型のL1データキャッシュを装備し、L2キャッシュを装備しない
  • ブロッキングなバス型の相互結合網 (インターコネクト) がL1キャッシュたちを調停する
  • コアとメモリ、そしてインターコネクトはAXI (Advanced eXtensible Interface) のサブセットに基づいて接続する

ソースコードを提示しやすいため、ISAにRISC-Vを採用します。 ベースラインは32bitですが、64bitでも本質は変わらないでしょう。 ただし、マルチコアにおける不可分操作の実装について議論するため、A拡張を採用することを想定します。

Out-of-order実行やロードキュー・ストアキューは筆者自身まだよくわかっていないため今後の課題とします。 なお、これらを用いないことによって常にシーケンシャル・コンシステンシが充足されるでしょう*5。 これは、あるコアのメモリアクセスが他のコアから見てどのような順番で実行されているかを制約する概念です。 一言で表すと、最も強い制約がすでに働いているということです。 プリフェッチもよくわかっていないためスキップします。

マルチコアで重要な概念であるキャッシュコヒーレンスについて議論するため、コアがL1キャッシュを装備していることを想定します。 特に、典型的なキャッシュコヒーレンス・プロトコルを採用するために、ライトバック型のL1データキャッシュを想定します。

インターコネクトはバス型とします。 また、一つのマスタがバスを利用し始めてから処理が完了するまでは、バスを占有し続けます (ブロッキング)。 性能向上の手法は動いてから適用しましょう。

本連載では、AXIとその拡張であるACE (AXI Coherency Extensions) のサブセットを採用します。 したがって、ベースラインでもAXI (のサブセット) が利用されていることを想定します。 今までオレオレプロトコルしか使ったことがなかったり、そもそもプロトコルを意識した設計と実装を行ってこなかったりした人はこの機会に学びましょう。 全てのコンポーネントを自作するなら勝手ですが、信頼性や情報量、デバッグのしやすさ、既存のIPとの接続性といった観点から、標準的な規格を採用することは重要です。 井の中の蛙を卒業し、大海に出ましょう。 標準的なもの*6を知ったうえで、性能向上などの明確な目標を持ってからオレオレプロトコルを設計しましょう。 AXIをわかりやすく解説している連載を紹介します。

www.acri.c.titech.ac.jp

ベースラインが明確になったので、マルチコア化の課題を議論できるようになりました。 とはいえ、その課題は大きく分けて二つしかありません。 ただし、それらはかなり密接に関係しています。

  • バスを適切に調停する
  • キャッシュコヒーレンスを維持する

バスの調停は特に不可分操作の実行で重要になります。 例えば、シングルコアではAMO命令のロードとストアの間に命令フェッチを行っても問題なかったかもしれませんが、マルチコアでは他のコアが容赦なくストアしてきます。 あるコアが実行するAMO命令のロードとストアの間に、他のコアがメモリの同じ場所を上書きすると、ロードした値が古いものとなってしまい、その後ストアされる値が狂ってしまいます。 その結果、システムが壊れる可能性があります (n敗)。 正しい動作のために、バスを適切に調停しましょう。

ベースラインでL1キャッシュたちをバスにつないでいるため、すでに基本的な調停機構は動いているでしょう。 マルチコア化では、後述するキャッシュコヒーレンスも考慮して、バスを改造します。

キャッシュコヒーレンスの維持は必須です。 各コアは自身のキャッシュにヒットする限り、そこに格納されているデータを使い続けます。 あるコアがデータをストアしたときに、他のコアが古いデータを使い続けていては、計算結果が狂ったり、システムが壊れたりする可能性があります。 例えば、mutexのロック変数が古いままでは、いつまでたってもクリティカルセクションに進入できなかったり、複数のコアが同時にクリティカルセクションに進入してしまう可能性があります (n敗)。 正しい動作のために、キャッシュコヒーレンスを維持しましょう。

ベースラインでは、キャッシュは必要に応じてバスに読み出しや書き込みのトランザクションを発行しているでしょう。 マルチコア化では、「他のキャッシュから最新のデータを読む」「他のキャッシュのデータを無効化する」という機能が必要になります。 これらを実現するために、AXIの拡張であるACEという規格に基づいてキャッシュとバスを改造します。 先に小出しにしておくと、キャッシュはReadSharedMakeUniqueと呼ばれるトランザクションを発行できるようになります。 また、ReadUniqueと呼ばれるトランザクションにも対応します。

以上、自作CPUのマルチコア化の背景と課題を述べました。 本連載で実装するマルチコアのブロック図を以下に示します。

本連載で実装するマルチコアの構成

次回から詳細な設計と実装を行います。 お楽しみに。


*1:西山悠太朗、井田健太「RISC-VとChiselで学ぶ はじめてのCPU自作 ―⁠―オープンソース命令セットによるカスタムCPU実装への第一歩」、2021年、技術評論社

*2:吉瀬謙二「新・標準プログラマーズライブラリ RISC-Vで学ぶコンピュータアーキテクチャ 完全入門」、2024年、技術評論社

*3:阿部奏太「Verylで作るCPU 基本編 第I部」、2024年、ミーミミ研究室

*4:不可分命令などが正しく実行されることを保証する必要があります。

*5:RISC-Vはシーケンシャル・コンシステンシよりも制約の弱いメモリコンシステンシモデル (RVWMO) を採用します。シーケンシャル・コンシステンシを充足することで自動的にRVWMOを充足するでしょう。

*6:AXIのほか、AvalonやWishbone、最近ではTileLinkなどが利用されています。