LoginSignup
131
112

More than 3 years have passed since last update.

計算の高速化のために必要なこと: メモリの観点

Last updated at Posted at 2020-06-20

前回の「各種メモリ/ストレージのアクセス時間,所要クロックサイクル,転送速度,容量の目安」は,思わぬ反響を呼んだので驚いております。

今回の記事は,その記事の活用編にあたる記事です。コンパイラのコード最適化を設計・実装する時に重要な観点を,つらつらと書いてみます。

原則: 高速なメモリ・ストレージは容量が小さい

前回の記事の結論を再掲します。

メモリ/ストレージの種類 アクセス時間 アクセス開始までの所要クロックサイクル 最大転送速度 容量
レジスタ 0.33〜1ナノ秒 1 96〜576Gbps換算 60〜256バイト換算
L1キャッシュ 1.33〜4ナノ秒 4程度 96〜576Gbps換算 32〜64KB
L2キャッシュ 4〜15ナノ秒 12-15程度 96〜576Gbps程度? 512KB〜1MB
L3キャッシュ 16〜50ナノ秒 〜50程度 96〜576Gbps程度? 16〜256MB
メインメモリ(DDR4) 100ナノ秒 100〜300程度 96〜272Gbps程度 4GB〜2TB
NVMe SSD 50マイクロ秒程度 50,000〜150,000程度 0.48〜33.6Gbps程度 (60〜4200MB/s程度) 〜2TB
SATA3.0 SSD 100〜200マイクロ秒程度 100,000〜600,000程度 0.14〜4Gbps程度 (17〜500MB/s程度) 〜4TB
SATA3.0 HDD (3.5inch) 10〜20数ミリ秒程度 10,000,000〜60,000,000程度 0.04〜0.8Gbps程度 (0.5〜100MB/s程度) 〜16TB

上に行くほど高速なメモリ・ストレージですが,上に行くほど容量も小さくなっていることがわかります。

なぜ高速なメモリ・ストレージの容量は小さいのか,この理由については,ぜひ考察してみてください。

解決策: 広い意味での「仮想記憶」

このような問題を解決するには,仮想記憶の考え方が重要となります。すなわち,実質的に広大な単一のメモリであるかのように見せかけるが,実際には高速で容量の小さいメモリを一時作業領域に効率よく使って,低速で大容量のメモリと組み合わせるということになります。

キャッシュメモリや,コンパイラで用いられるコード最適化技法の1つであるレジスタ割当の最適化も,広い意味での仮想記憶だと捉えることができます。

原則: CPUに比べて,メモリやストレージ,I/Oは遅い

前回の記事の結論をもう一度見てみましょう。

メモリ/ストレージの種類 アクセス時間 アクセス開始までの所要クロックサイクル 最大転送速度 容量
レジスタ 0.33〜1ナノ秒 1 96〜576Gbps換算 60〜256バイト換算
L1キャッシュ 1.33〜4ナノ秒 4程度 96〜576Gbps換算 32〜64KB
L2キャッシュ 4〜15ナノ秒 12-15程度 96〜576Gbps程度? 512KB〜1MB
L3キャッシュ 16〜50ナノ秒 〜50程度 96〜576Gbps程度? 16〜256MB
メインメモリ(DDR4) 100ナノ秒 100〜300程度 96〜272Gbps程度 4GB〜2TB
NVMe SSD 50マイクロ秒程度 50,000〜150,000程度 0.48〜33.6Gbps程度 (60〜4200MB/s程度) 〜2TB
SATA3.0 SSD 100〜200マイクロ秒程度 100,000〜600,000程度 0.14〜4Gbps程度 (17〜500MB/s程度) 〜4TB
SATA3.0 HDD (3.5inch) 10〜20数ミリ秒程度 10,000,000〜60,000,000程度 0.04〜0.8Gbps程度 (0.5〜100MB/s程度) 〜16TB

特に「アクセス開始までの所要クロックサイクル」に注目してください。レジスタへは1クロックでアクセスできるのに対し,メインメモリは100〜300程度のクロックサイクルを待たないとアクセスできません。最速のSSDであるNVMe SSDであっても50,000〜150,000程度のクロックサイクルの待ちが発生します。HDDに至っては10,000,000〜60,000,000程度のクロックサイクルもの間,ひたすら待つことになります。

下記論文によれば,このような「メモリストール」によって8〜65%もの実行時間を浪費しているとのことです。

Todd C. Mowry. 1998. Tolerating latency in multiprocessors through compiler-inserted prefetching. ACM Trans. Comput. Syst. 16, 1 (Feb. 1998), 55–92. DOI:https://doi.org/10.1145/273011.273021

解決策: 適切なタイミングでプリフェッチすることが大事

この問題を解決する方法は,データが必要になる前に読み込み指令を出しておくこと,すなわちプリフェッチすることが重要です。

メインメモリであれば,実際にループを回して計算を開始する100〜300クロック前にプリフェッチしてデータをキャッシュに載せておくことが重要になります。一旦L1キャッシュまで入れば,現代的なCPUは数々の予測機能を駆使してくれて,パイプラインがストールしないように前もってデータを読み込んでくれます。

プリフェッチをプログラムで明示的に指定するコード最適化技法を software-controlled prefetching といいます。先ほどの論文は,このような技法に関する研究論文です。

もちろん,高速なメモリは容量が小さいので,むやみやたらとプリフェッチしてしまうと,あふれてしまいます。

この辺りについて,気の利いた感じでコード最適化するというのは,まだまだ研究の余地が多々ある領域ではないかと思います。

131
112
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
131
112