例を挙げると、次の図の2行目のcudaMallocManagedでユニファイドメモリ領域を確保し、3行目ではcudaMemsetで0を書き込んでいる。この動作はGPUで行われている。そして、次のforループはCPUで実行されるので、CPUのアクセスに応じて、必要なページがCPUメモリに移動されて計算が行われる。
最後のReduce_sumはGPUで実行されるカーネルであるので、GPUのアクセスに伴ってページがCPUからGPUに移動されるという動きになる。
このプログラムをK40 GPUで実行させた場合のプロファイラ出力が次の図である。まず、Mallocが両方で行われ、次のMemsetはGPUで実行され、CPUでアップデートが行われる。CPUがアクセスするデータが、CPUメモリに存在しないので、GPUメモリから領域全体がコピーされてきている。
そして、後のReduce_sumはGPUで実行されるので、領域全体のGPU側へコピーが発生している。
ヒストグラムの作成や動的なキューの作成のような処理は、どの区分に幾つのデータが入るかはやって見なければ分からない。このため、溢れないように余裕をもってメモリを確保しておく必要がある。
しかし、Pascalでは、デマンドページがサポートされたことで、必要になった時点でメモリを使うので、GPU側のメモリは必要になった時点で確保すれば良いため、メモリを浪費しないで済む。
また、グラフを辿る問題では、膨大なサイズのグラフの内のどのノードが使われるのかはデータ依存で、実行してみないと分からない。大きなグラフの場合は、グラフ全体をGPUメモリに入れられないが、デマンドページであれば、使われるノードのデータだけをGPUメモリに持って来ればよい。
グラフ問題では、どのノードがアクセスされるかは、データ依存であり事前には分からない。全ノードのデータをGPUメモリに入れられない場合でも、デマンドページなら、必要なノードのデータだけをコピーしてくれば済む |
グラフの各エッジには、それぞれ、最大流量が決まっており、始点から終点のノードまでの最大流量を求める最大フロー問題も、このような問題である。
最大フロー問題を扱うには、KeplerやMaxwell GPUでは、アクセスされる可能性のあるすべてのグラフデータをGPUメモリに置いておく必要がある。GPUメモリに入らない部分は、CPUメモリをマップして使用したり、アプリで入れ替えを行なったりする必要がある。しかし、Pascalの場合は、アクセスする部分のデータだけがオンデマンドで移動されるので、グラフ全体のデータをGPU側で持つ必要がない。このため、GPUメモリに収まらない大きなグラフでも簡単に処理できる。
Kepler、Maxwellでは、グラフの全データをGPU側メモリに置く必要があったが、Pascalでは、オンデマンドで必要なデータだけを持ってくるので、GPUメモリに入らない大きなグラフの処理も簡単に行うことができる |
アレイの中にポインタが含まれているディープコピーのケースでは、アレイだけをGPUメモリにコピーしても、ポインタの先のデータをコピーしなければ、データをアクセスできない。このためには、GPUメモリにコピーを入れるメモリを確保し、コピーを行って、ポインタを付け替えるという処理が必要であり、かなり手間が掛かる。
しかし、デマンドページングのユニファイドメモリになると、ポインタを含むアレイをコピーした状態で、ポインタで指されたデータをアクセスしようとすると、そのデータはGPUメモリには存在しないので、デマンドページングでそのデータをGPUメモリに持ってきてアクセスできるようにしてくれる。従って、ディープコピーのケースでも意識せずに処理ができる。成瀬氏のスライドには、ディープコピーのケースは含まれていないが、これもPascalのユニファイドメモリで可能になった重要なケースである。