「 Geforce vs Radeon 」一覧

DirectX9 on Windows 7 でメモリの消費調査


今回は DirectX 9.0 を用いた場合ではどんな感じになるのかを調べてみました。環境は Windows 7 (x64) です。また DirectX 9.0 と表記していますが、実際のところは DirectX 9.0 EX のことを指すものとします。

Windows XP までの頃は VRAM の仮想化というものがなく、デバイスロスト発生のためのケアが必要になっていました。その代わり VRAM にデータを送ってしまい、メインメモリは消費しないということができたように思います。(もしかするとできていなかったのかもしれませんが、今更検証価値も低そうなので手元でテストしていないです・・・。)
一方で Windows 7 になってVRAM仮想化が導入され、VRAMの空き容量など取得が難しくなってきました。 DirectX 9.0 は動くものの、その挙動は XP のころとはまた違ったものになっていると予想されます。それなのに検証をしていないままだったので、 VRAMの情報取得という武器ができた今、調べてみようと思ったわけです。

NVIDIA (Geforce9800) の場合

安定状態になってから頂点バッファ 32MB を D3DPOOL_DEFAULT にて作成しました.

状態 メモリ(MB) Dedicate(MB) 共有メモリ(MB)
定常状態 1054.6 47 13
VertexBuffer作成 1回目 1086.7 80 13
VertexBuffer作成 2回目 1119.3 112 13

どの場合においてもメインメモリ 32MB 程度消費し、Dedicate のメモリ 32MB ほど消費している結果となりました。

AMD (RADEON) の場合

状態 メモリ(MB) Dedicate(MB) 共有メモリ(MB)
定常状態 1028.9 45 22
VertexBuffer作成 1回目 1061.1 77 22
VertexBuffer作成 2回目 1093.8 109 22

こちらの場合においてもメインメモリ 32MB 程度消費し、Dedicate のメモリ 32MB ほど消費している結果となりました。

実験その2

描画フレームを回しながら、 100 フレームごとに頂点バッファを確保し描画するという定番のプログラムで実験してみました。
上記の消費メモリが永続的なものではなくテンポラリ扱いならば、相当の数のバッファが作れるはずです。

しかし予想外の結果になりました。

メインメモリの消費が、自分のプロセスに対して発生していました。どうやら D3DPOOL_DEFAULT では自プロセスのどこかにバックアップのバッファが存在するようです。

GTX 750Ti 2GB の状態で普通の 32bit アプリケーションで実行の場合には、x64環境といえども、 1プロセスで使えるサイズは 2GB まで。そのため VRAM 使用量が 1GB 程度のところで DirectX がバッファの作成に失敗しました、とエラーを出しました。このときのプロセスの状況を調べてみましたが、消費されている仮想アドレスが 2GB 近かったです。
 一方で、 32bit アプリで LAA オプションをつけて再実行してみると、今度は VRAM 使用量は 1.7GB 程度にまでなり、プロセスの仮想アドレス使用量は 4GB 近くにまでなっていました。

※ このプロセス内での状況を知るのに VMMap というツールは便利です。

まとめ

DirectX 9.0 の場合においては NVIDIA も AMD も同じような挙動となることを確認できました。またどちらの場合においても使用するリソース分、メインメモリも消費していることが確認できました。これはこれで挙動把握さえできてしまえば、扱いやすそうです。
ただ注意が必要であると感じたのが、自分のプロセスの仮想アドレス空間にバックアップ先を確保するため、メモリ不足よりも自身の仮想アドレス空間を枯渇させてしまうのが早そうに思います。 64bit 環境を必須とするなら 32bit アプリケーションの場合、 LAA 必須といってもいいレベルだと思いました。
 この場合、よいビデオボードを使っていて 「 4GB の VRAM 全部使いきる DirectX 9.0 アプリケーションを開発してやるぞ。 ただし 32bit アプリケーションだけどなっ」 という夢はかなわないことが判明しました。そんな人は少数派だとは思いますが・・・。


DirectX11の場合のシステムメモリとVRAM消費について


今までは OpenGL の場合を調査してきました。今回は DirectX11の場合を調べてみようと思います。ドライバが一緒ならきっと同じ傾向を示すんじゃないかなと予想して実験をスタートです。

実験

DirectX11のプログラムとして、100フレームごとに頂点バッファ 32MB を確保し、次フレーム以降はこのデータを使って描画するプログラムを作成しました。
各フレームでの各メモリの使用状況を取得して、グラフ化しました。

実験は以下の環境で行いました

  • GTX750Ti Windows 7 347.09
  • RADEON HD 5450 Windows 7
  • RADEON HD 5450 Windows 8

結果

それぞれの実験結果のグラフを以下に示します。

nvidia_memory_graph_dx11_win7

radeon_memoy_graph_dx11_win7

radeon_memoy_graph_dx11_win8

NVIDIA も AMD も同じような挙動を示す結果となりました。また VRAM が使用可能な状態であれば、追加のシステムメモリの消費もなく良好といえそうです。(とはいっても追加のメモリを要求されたのは NVIDIA OpenGL の場合のみでしたが)。さすが仕様が決まっている DirectX といえるかなと思います。
 ちなみに RADEON のほうのグラフ末尾がおかしなことになっていますが、このときアプリは相変わらずフリーズ状態になってしまいました。ですので、システムメモリを VRAM リソースとして使用始めたらやや注意が必要になっていきそうです。ビデオメモリ仮想化が入ったといわれる現時点においても、なるべく搭載量を超えないようにするのはマナーとしておいたほうがよさそうです。


OpenGLでOut of memory とメモリの関係 (AMD)


今まで NVIDIA のもので調べていたので今回は同じプログラムを AMD Radeon 5450 を使ってデータ取得してみました。

  • Windows 7 x64 (2GB)
  • AMD Radeon HD 5450 (1GB)
  • ドライバは Catalyst Omega 14.12

プログラムは OpenGL を用いて 32MB 単位で頂点バッファを 300 フレーム間隔で確保していくものです。作成後、 Out of memory のエラーが返ってきたら終了します。

実験結果

何度か動かしてみましたが、 GL_OUT_OF_MEMORY のエラーが取得できませんでした。あふれた状態になったと思われるときには、アプリケーションがフリーズ状態に陥りました。 SwapBuffers の実行内部で無限ループしているような気配でした。
 この状況になるまでのデータで、システムの使用メモリ、 VRAM の使用量、共有メモリの使用量をグラフ化してみたものが以下のようになります。 NVIDIA の場合とは傾向が違っていておもしろいです。

radeon_ogl_outofmemory_log
今回の場合は、 VRAM に配置できる場合にはそちらに配置して、システムのメモリは消費しないようにみえます。実際にこのタイミングではプライベートヒープが増えているような感じではありませんでした。 観測タイミング次第によっては glBufferData の中身のテンポラリの DMA 転送タイミングで作業バッファぽいものが見えたりしますが、数フレーム後解放されたりしました。 VRAM に配置できない状況になったときに、初めてシステムのメモリを消費しての共有メモリ使用増加が観測できました。おおよそ 1.6GB ほどの使用量になったときにアプリケーションがフリーズ状態に陥ったように見えます。この環境ではビデオアダプタのプロパティで確認できたのが「利用可能な全グラフィックスメモリ 1791MB」とあり、最大値であるこの値にまで到達したのかなと思っています。

感想

AMD のグラフィックスドライバは余計な(?)システムメモリを消費しなかったので、 OpenGL 実装でも無駄はないという点に関心しました。一方で、メモリあふれのエラーを検知できないのはマズイですね。無駄なメモリは確保しないから、溢れさせないよう気をつけてアプリケーションを実装しましょう、ということでしょうか。

おまけ

同じようなテストを Windows8 (64bit) に切り替えて行ってみました。おおよそ同じ傾向になりました。ただシステムメモリの細かな変動が観測され、作業メモリとして使用している部分がみえたのかなと思っています。(これは以前に glBufferData 直後での内部動作でそのような動きを見せたためです。)
ドライバが標準的に導入されたものを使用し、 Catalyst Omega ではない点ももしかすると影響しているかもしれません。
radeon_ogl_outofmemory_log_win8


OpenGL @NVIDIAでのメモリ調査


NVIDIA Geforce で OpenGL を使った場合のシステムメモリの消費について調べてみましたが、サイズ可変でどのくらい消費量が変わるのか、というものでした。また API 実行の各タイミングで計測したのみで、時間経過を考慮していませんでした。

今回はサイズは固定し、 100 フレームに 1 回頂点バッファを確保して描画するという方法を試してみました。また描画に使用されない場合は処理をされないという可能性を除外するため、次フレーム以降は確保したバッファを用いての描画を行うようにしています。

確保のタイミングで、以前の確保時のデータとどのくらいメモリ使用量が変動するかをグラフ化したものが以下となります。
nvidia_opengl_temporary

結果

NVIDIA の場合、 OpenGL の作業用のメモリとして多めのメモリを使用する傾向にあるようです。しかしフレームの経過とともにその作業メモリを解放も行っているようです(そうでないと差分デルタが減った説明ができない)
多くの場合は、 VRAM 側に確保したメモリサイズと同じ程度のメモリを消費すると考えて良さそうです。


OpenGLでOut of memory とメモリの関係


細かな単位で OpenGL の頂点バッファをたくさん確保していたらこんなエラーに出会いました。ちなみにプログラムの実行タイミング次第では OpenGL で OUT_OF_MEMORY のエラー状態がちゃんと返りました。エラー返す前に異常系に落ちたという感じでしょうか。
nvidia_driver_oom

この症状が出るまでの様子を、使用システムメモリ、使用VRAM量、使用中の共有ビデオメモリ量、との値をグラフにしてみたものが以下となります。

nvidia_ogl_outofmemory_log

実験プログラムについて

Windows 7 x64 (16GB) の環境で、 Geforce GTX750 Ti (2GB) を装着しています。
300 描画フレーム間隔で、 128MB の単位で頂点バッファを確保するプログラムを動作させています。また、確保後は次のフレーム以降、バッファ先頭だけ使ってではありますが描画処理も行っています。

このようにしている背景としては、描画されないデータは処理されないのでは?という点と SwapBuffers が呼ばれない場合、作業メモリが解放されないのではという疑惑があるためです。そのために、描画ループを回しつつ、定期間隔で確保実験するに至りました。

この結果について

実際の搭載 VRAM 量までは順調に VRAM 側にリソースが確保されるようです。それがいっぱいになると共有メモリを使うようになっていくのが見えます。このとき、ドライバのなかでデータの整理が行われるのか、一時的に逆転送など行い共有メモリ量が増加&VRAM側をパージしている様子がうかがえました。
また VRAM に確保されようが、システム全体の使用メモリは増加していくのがわかります(黒線のグラフ)。使用した VRAM リソースの合計は約3GBでした。

実験その後

VRAM の断片化とかあるかなと考えて、バッファのサイズを 32MB に減らして再実験しました。このときアプリケーションもまた 32bit ですが、 LargeAddressAware をオンにして 4GB までのアドレス空間使えるようにしてみました。
 しかしながら、4GB まで使えるようになったというような結果を予想していましたが、3GB付近でエラーとなってしまいました。アプリケーションのアドレス空間の問題ではないようです。エラーが出た際のプロセスの状況を探ってみると、 1.3GB 程度のメモリ使用量となっており、このときプライベートヒープで割としめられていました。そのブロック単位も 32MB ほどなっており、どうやら VRAM リソースとして確保した何かのコピー(もしくは実体の待避?)かと思われます。これらの情報は VMMap を見れば詳細にわかります。

このような状況になってもちゃんと動き続ける今の Windows や NVIDIA のドライバはすごいなぁと関心もした次第です。


OpenGL API とメモリの消費について NVIDIA特別編


どうにも NVIDIA のメモリ消費について、メモリ消費しすぎな感じがあったので追加調査してみることにしました。

32MB VBOの場合(前回の結果)

状態 メモリ(MB) Dedicate(MB) 共有メモリ(MB)
初期化終了後 999.8 36 13
元データ準備 1031.7 36 13
glBufferData実行後 1114.7 68 32
元データ破棄 1082.7 68 32
初回描画後 1082.9 70 32

そこで今回はサイズを変化させた VBO 2つでどのように変化するか、しないかを見ていきたいと思います。

4MB VBO x2

状態 メモリ(MB) Dedicate(MB) 共有メモリ(MB)
初期化終了後 1023.0 36 14
元データ準備(4MB x2) 1037.9 36 14
glBufferData (1)実行後 1050.8 40 16
glBufferData (2)実行後 1054.8 44 24
元データ破棄 1043.4 44 24
初回描画後 1043.4 45 25

BufferData2回分の内部データとして 16.9MB を消費、定常的には 20.6 MB の消費という結果になりました。VRAM のリソースとしては 4MB x2 = 8MB = 44-36 と想定に合うサイズが使用されています

16MB VBO x2

状態 メモリ(MB) Dedicate(MB) 共有メモリ(MB)
初期化終了後 1019.1 36 14
元データ準備(16MB x2) 1048.2 36 14
glBufferData (1)実行後 52 32
glBufferData (2)実行後 1133.2 68 36
元データ破棄 1101.2 68 36
初回描画後 1101.4 70 37

BufferData2回分の内部データとして 85MB を消費、定常的には 82.2 MB ほどメモリを使用する結果になりました。 VRAM リソースとしては 70-36 = 34 MB となりおおよそ想定の範囲の結果に。

64MB VBO x2

状態 メモリ(MB) Dedicate(MB) 共有メモリ(MB)
初期化終了後 1023.9 36 14
元データ準備(64MB x2) 1156.0 36 14
glBufferData (1)実行後 1302.1 100 32
glBufferData (2)実行後 1433.2 164 36
元データ破棄 1304.9 164 36
初回描画後 1309.3 165 37

BufferData 2回分の内部データとして 277.2MB, 定常状態として 285MB の消費となっています。

まとめ

サイズを変更させて実験してみましたが、元データサイズの倍以上は消費されると考えて良さそうです。
これらの内容をグラフ化すると以下のようになります。なかなか作業メモリ(というかバッキングストア)分がひどいことに。

nvidia_ogl_working_memory_usage

追加

別視点から実験してみました。OpenGL @NVIDIAでのメモリ調査


OpenGL API とメモリの消費について AMD限定編


前回は NVIDIA, AMD と両者での差異を見るために同じコードで実験しました。今回は AMD ならではの OpenGL 拡張を用いてどう変化が起こるかを見てみたいと思います。

AMD_pinned_memory

拡張に AMD_pinned_memory というものがあります。これは GPU から物理メモリへのアクセスをさせるという拡張です。
こちらを用いると、以前実験したように中身を書き換えても再送コマンドの実行が必要なかったりします。

実験

pinned_memory の機能を用いて前回と同じように、頂点バッファ (VBO) を作成してみます。この際に使用メモリと使用 VRAM がどのように変化するかを観測します。
確保する頂点バッファは 32MB とし、今回もまたプロセスヒープの影響がないように、それと、アライメント調節の手間を減らすため OS からの直接ページのアロケートを使っています。

実験結果

実験は RADEON 5450, 7750 で行っていますが、傾向が異なることはなかったので、 5450 のほうを記載しています。また pinned memory の場合は元データ解放処理ができないのでその項目は除外しています.

状態 メモリ(MB) Dedicate(MB) 共有メモリ(MB)
初期化終了後 1193.7 44 19
元データ準備 1226.2 44 19
glBufferData実行後 1226.2 44 19
初回描画後 1220.1 47 52

32MB の VBO は Shared Memory の扱いとして処理されたようです。おおよそ 32.5 MB 消費されています。
注目すべきは glBufferData の内部で追加のメモリが要求されていないことです。前回 NVIDIA, AMD 双方で標準的な方法で glBufferData を実行の際にはこの部分で内部で勝手に追加のメモリを消費されていました。また初回の描画で初めてリソースが確保されるようです。

まとめ

pinned memory 拡張は暗黙のメモリ確保がないという点でよさそうに感じました。前回の時もそうでしたが、 AMD の場合には定常状態においては、元データサイズ分程度のシャドウコピーがどこかに存在するようです。 NVIDIA の場合と違い、バッファサイズ分を見積もっておけば良さそうなのでまだ制御がしやすそうです。


OpenGL API とメモリの消費について


使用中 VRAM の計測ができるようになったので、 OpenGL API を使ったときにどのようにメモリが使われていくのかを調べてみました。特に OpenGL の場合にはブラックボックス化された何かという印象が強く、暗黙に消費部分が多いのでは?と感じているためです。この記事エントリ読んでいる方も、そんな風に感じていたりするかもしれません。

環境

プログラム開発PCと調査対象PCを分離して、なるべく影響しないようにして計測しました。また折角なので NVIDIA Geforce, AMD Radeon のそれぞれで調べてみました。Intel HD Graphics はターゲット環境として用意できなかったので未計測です。
使用した OS は Windows 7 64bit で、各ボードでの最新ドライバを使用しました。

  • Geforce 9800 340.52
  • RADEON HD 5450/7750 Omega 14.12

ボードがちょっと古いですが、傾向はつかめるかなと思います。

免責事項

本調査結果の無断転載を禁止します。本調査はあくまで個人の趣味&好奇心から調べたものであり、結果および考察内容への責任は負いかねます。

実験内容

OpenGL を用いて頂点バッファ(VBO)を作成して、描画。このときに各フェーズでメインメモリと VRAM においてどのように消費されていくかを記録しました。基本的にはOpenGL 2.1世代で標準と思える機能のみを使用して、最近の OpenGL 4.4 系の拡張は未使用です。この状態で、バッファタイプは GL_STATIC_DRAW を設定して、データは VRAM へ配置されることを期待したプログラムを作成しました。
また頂点バッファのサイズは 32MB としました。そして転送元データはOSからの直接アロケートを使用し、プロセスのヒープによる影響を受けないよう工夫しています。

NVIDIA Geforce の場合

NVIDIA の場合には、メモリ使用量の変動幅が大きく、複数回の平均をとっています。

状態 メモリ(MB) Dedicate(MB) 共有メモリ(MB)
初期化終了後 999.8 36 13
元データ準備 1031.7 36 13
glBufferData実行後 1114.7 68 32
元データ破棄 1082.7 68 32
初回描画後 1082.9 70 32

上記のような結果になり、データを転送する際には元データ32MBに加えて glBufferData 内部でさらに追加の作業メモリを使用しているようです。それも単に元データのシャドウコピー 32MB という単位ではなく、約 83MB と2倍ちょっと使用しています。

AMD RADEON の場合

AMD の場合にはメモリ変動幅が NVIDIA に比べて低めでした。

状態 メモリ(MB) Dedicate(MB) 共有メモリ(MB)
初期化終了後 1195.7 44 19
元データ準備 1228.0 44 19
glBufferData実行後 1292.2 44 19
元データ破棄 1260.2 44 19
初回描画後 1229.9 79 20

上記のような結果になった. glBufferData 内部でのメモリ消費量は 64.2 MB となり、元データの倍ほどの作業メモリがあるようです。初回描画後は転送に使った作業メモリなどが解放されているためか、初期化終了後からバッファ分程度のメモリ増加にとどまっています。(1229.9-1195.7 = 34.2 MB)
また glBufferData 実行では VRAM が消費されていないことがわかりました。実際に処理されるのは初回描画される直前、もしくは、コマンドに積まれてGPUが実行するときまで遅延されるということでしょうか。

まとめ

NVIDIA にしろ AMD にしろ、 glBufferData で作業メモリを消費します。ここまではすでに予測している人も多かったと思います。しかし元データのコピー分だろうと見積もっていたはずです。今回の計測によりそれはハズレであることがわかりました。
少なくとも、元データ以上のサイズが消費され、今回の場合では2倍以上必要になるケースがあることがわかりました。
そして定常状態になった時、 AMD のほうは元データ分だけが消費されているのに対し、NVIDIA は glBufferData 実行直後からおおよそ変動しません。今回のケースでは 32MB の頂点バッファなのに、約 83MB もの領域を消費してしまっています。

メモリがシビアになるケースでは、このことを考慮してプログラムを作成した方がよいかもしれません。


NVIDIA Cgランタイムについて


またも今更な話題にはなるのですが、NVIDIAから出ているCg ToolkitのCgランタイムについてです。

NVIDIA製のグラフィックボード以外で使用することの保証はないだろう!と考え、他のプラットフォームではたまたま動いている、と考えていたのですが、どうやらそれは違うのかもと思う記事を見かけました。

http://www.nvidia.ru/object/devnews028_jp.html
以前のニュース記事なのですが、ここによると
「Complete rewrite of CgFXの完全書き換え。CgFXがあらゆるプラットフォーム上で作動するようになり、NVIDIA以外のOpenGLドライバ上でも作動するようになりました。CgFXの実質的な説明書は 後続のCg 1.4のビルドに含まれます」

となっており、他のOpenGL環境でも動くように対応作業があったことが推測できます。つまり今Radeonでも動いているのはコレが理由としてあったからなのかなと思います。


(OpenGLの)シェーダーバイナリ(GLSL Binary)


そういえばOpenGL 4.xでシェーダーのコンパイル結果をバイナリとして取得、ロードできる機能が追加されたのですが、当時ドライバがまだ対応していなくて見送っていました。ようやくこれについて調べてみることが出来たので、ここに書いておこうと思います。

まず、DirectXではシェーダーをコンパイルすると各ベンダで共通なシェーダーアセンブリ状態になります。このアセンブリ仕様はDirectXで決められているためにベンダ間で共通データとなります。

OpenGLのGLSLをコンパイルした結果はどうなるのか、これが気になるポイントでした。
DirectXのようにベンダー間で共通のデータとなるようなら便利に使えそうです。また同機能ということでOpenGLがDirectXに追いついた!とも見えるのではと思います。

そこで手持ちの環境で同じシェーダーをコンパイルしてバイナリデータを記録してみました。
ここで用いたのは下記のシェーダーです。

このシェーダーをNVIDIA(Geforce)のボードが刺さっている環境、AMD(RADEON)のボードが刺さっている環境でそれぞれ実行してみました。

この部分はファイル先頭なのですが、この時点からして既に全く違います。
AMDのほうではシェーダーのソースがほぼ見えるのに対し、NVIDIAのほうはそんな様子はありません。
ファイルを適当に中間部分まで表示させてみると以下のようになります。

AMDのほうはよくわからないデータが入っているようです。一方NVIDIAのほうではシェーダーアセンブリっぽいものが格納されているのがわかります。

AMDのほうはバイナリファイルにELFという単語が見えますし、GLSLソースコードが入っていたりとあまりシェーダーバイナリっぽくありません。NVIDIAのほうもNVIDIA拡張のGLのシェーダーアセンブリとして書かれているようで、両者全く違うというのが明らかになったかと思います。

DirectXもシェーダーバイナリはいわゆるアセンブラなのでこの点を考えるとNVIDIAのやっている方が近い感じだと思います。コンパイルされてアセンブリになっているという点が一緒というわけです。
一方でAMDのほうはソースが入っているだけのようにしか見えません。

これらの結果からやはりOpenGLのシェーダーバイナリは実行環境でのシェーダーコンパイル結果のキャッシュデータとしての価値しかなさそうです。実行環境が決まらないようなプログラムではGLSLソースコードを保持しておくしか手がなさそうです。