Sid MeierからCivilization4が発表されたばかりの2005年のことです。 その時までに、私はCivilization3にしっかりとぶら下がって、さまざまなマップで20回それを通り抜け、ここに待望の4つが来ました。 これらは、ミッドエンドではP3-512Mb、ハイエンドではP4-1Gbでした。 それらの年のトップエンド構成のみが、2ギガバイトのメモリを搭載していました。
Civilization 4は2002年から2003年のレベルでリリースされました。これは基本的に当時の主流では普通のことで、特にこれはシューターではなくターンベースの戦略であることを考慮しています。 しかし、ゲームの過程で最大900MbのRAMを消費し、特に大きなマップで、特にゲームの終わりに向かって、特にラップトップでひどいスワップが発生しました。 人々は困惑していたので、私もそうでした。 同じ年にはるかに美しいグラフィックでFar Cryが登場し、512Mbを搭載しても最大でうまくいったことを考えると、Civilization 4のこの動作は非常に奇妙に見えました。 私はそれを理解して罰したかった...
だから、私はピッキングを始めました。 Pythonに最初の疑念が生じたのは、 Firaxisはその使用をあらゆるターンで重要な機能として言及しており、ガベージコレクターがあり、ピーク負荷後にメモリが解放されず、さまざまな楽しみがあります。 これは、大食いの容疑者の第一候補でした。 Python dllのソースコードがダウンロードされ、すべてのメモリ割り当てのロギングが追加され、ネイティブDLLの代わりにdllが文明に組み込まれました...興味深いものは何も現れませんでした。 pythonに残された25Mbのメモリを禁止しています。 他の過酷な人が資源を食べていました。
メモリリークの概念は、通常のC-shnyhメモリ割り当てに忍び込みました。 DLLでCRT(Cランタイムライブラリ)が使用されていたため、malloc、realloc、free、...のすべての呼び出しでalatourフックがハングし、結果もゼロになりました。 それから、すべての理由は、おそらくPythonのせいでさえも、頻繁な再割り当てでのメモリの断片化であると考えられていました。 私はマネージャーを書きました。マネージャーは常に次数2の倍数のボリュームを割り当てます-無駄に。 文明は800Mbで食べて、食べ続けました。 私は少しst睡状態に陥りました-どこでそんなに多くの記憶に行きますか。
CS:EIP呼び出し元の計算で、すべてのVirtualAllocにAPIフックを掛けて、civ4.exeまたはpython24.dllがアドレススペースのほとんどを割り当てていることを確認しました。 そして、d3d9.dllがそれを食べていたことが判明しました。 これはすでにより興味深いものになっています。すべて(またはほとんどすべて)がビデオメモリにあるのに、なぜ彼はリソースを食べるのでしょうか。 その後、デバイスの作成からテクスチャ、頂点、インデックスバッファの作成まで、DirectXの呼び出しを修復し始めました。
Civilization4が何をどのように行うかを研究すると、D3DPOOL_DEFAULTのリソースの一部を保持していることが判明しました(後に、これらはサードパーティ企業
Scaleformによって書かれたグラフィカルインターフェイスのリソースであることが判明しました
。CryTekの
人でも製品を使用しているため、 ) Civilization4は他のすべてを管理対象プールに保持し、500MBのメモリを消費しました。
DirectXのリソースのMANAGEDおよびDEFAULTプールに関する小さな余談。 ビデオメモリは一度に複数のアプリケーションで使用できます。また、その中に目的のテクスチャまたは頂点/インデックスバッファがあるかどうかは、何かを描画するために重要です。 DEFAULTプールの場合、あるプロセスのビデオリソースを別のプロセスに置き換えると、ビデオメモリが愚かに上書きされ、失われた領域を使用する後続の描画操作は「オブジェクトが失われました」というエラーを返します。 このような損失に対する応答は、ディスクからテクスチャを再読み込みし、ビデオメモリにテクスチャを再作成することによるオブジェクトの復元です。
MANAGEDプールの場合、メカニズムはほぼ同じですが、すべてのテクスチャと他のリソースはDirectXによってRAMにキャッシュされ、エラーへの反応を透過的にします-通常のメモリと同様に、他のプログラムによって上書きされた場合、RAMからビデオメモリのコピーを復元します「バックアップ」はスワップファイルです。 これにより、レンダリングサイクル中にビデオオブジェクトを再読み込みする必要がなくなるため、プログラマーの生活が簡素化されますが、特にゲームがほとんどの場合フォアグラウンドにあり、誰とも共有せず、十分なビデオメモリがある場合は、RAMの消費が不当に増加します過剰に。
残念ながら、コンベンショナルメモリとハードドライブの類推は、最後まで機能しません。 ハードディスクのボリュームは、通常、オンボードメモリの量の100倍です。したがって、RAMと同等のスワップファイルのサイズに問題はありません。 RAMとVIDEOMEMの比率は通常100/1ではなく約4/1です。 したがって、MANAGEDは生産ソリューションではなく、怠ratherな人向けのモードであり、それほど重くない些細なことに適しています。 MANAGEDですべてを一列に並べると、図のようになります。
深刻なエンジンはすべて、デフォルトのプールで動作し、ディスクからオブジェクトを再読み込みするか、極端な場合、RAMの独自のキャッシュを使用しますが、MANAGEDプールは使用しません。 実際、DEFAULTプールで作業しているときにリソース損失の状況を正しく処理できることが、Alt-Tabへの(非)フレンドリーゲームの理由です。
最初に、オブジェクト作成のフックを使用して、すべてをDEFAULTプールに変換しようとしました。 写真が落ちました。 最初は何が起こっているのか理解できませんでしたが、その後思い浮かびました...当然、500Mbはどのビデオメモリにも収まりません(当時、トップエンドのビデオカードには256Mbが搭載されていました)。 彼らがMANAGEDプールを使用した理由が明らかになりました-独自のグラフィックオブジェクトでさえ、ビデオメモリとaltタブの他のプロセスとの競合は言うまでもなく、ビデオメモリに収まりませんでした!
また、スワップが沈静化しない理由も明らかになりました。 ビデオメモリが過剰な場合、RAMの管理対象部分が徐々にスワップに移動し、実際に使用されているページによって絞り出され、ブレーキが消えます。 しかし、この管理されたキャッシュは常にアクセスされるため、これは発生しませんでした。 一般的に、すべてが明確でした。 1つのCom睡。 Far Cryには512MbのRAMと128Mbのビデオがありませんでした。 そして、
そのグラフィックスで! Civ4は、最近転送する場合、2005年レベルのスケジュールで5GbのRAMが必要でした。
DirectXレベルで既にメモリリークを検出しようとしても、結果は得られませんでした。 私は早期の節約を出荷します-低メモリ消費。 遅延保存-クレイジーメモリ消費を出荷します。 何かが流れた場合、それはセーブに流れ込みます-私は間違いなく、そのような漏れをsorsなしで追跡しません。 私はどこかで何かが無駄になっているという仮説から始めます...私はテクスチャを最も重いものとしてチェックし始めます。
テクスチャをチェックすると、別の驚きにつながりました。 低では50Mb前後、高では120Mb前後がありました。 残りの400MBはどこに行きましたか? Direct3Dを介して呼び出されるすべてのログを記録し始め、400メガバイトの頂点バッファーを見つけました! 頂点バッファーは、ジオメトリデータ、ユニット、都市、地形の3次元モデルです。 フレームごとにすべてのユニットのアニメーションを描画したという悪い考えが頭に浮かび始めます。何もする必要はありません...私の良心を落ち着かせるために、FVFによって頂点バッファーでメモリ消費を並べ替えます(頂点の形式は、それが何であり、何がそうでないか、たとえば、座標、照明、テクスチャへのスナップなど)。 頂点バッファにはいくつかの種類がありますが、280Mbを消費するものを除き、すべてがあまり消費しません。
バッファの種類を調べる方法は? データ、そのテクスチャ、その頂点バッファが入力されると、最初にロックされ、次にデータが入力され、ロックが解除されます。 私はそこにはんだ付けします-ロック解除で。 ロックを解除する前に、頂点の座標にランダムな値を混ぜて、何が変わったかを観察します。 兵士たちが腕や脚をひきつり始めると思います。 しかし、地獄...地球は泳いだ! そして、ここで悟りは私に軽desします。なぜゲームの終わりまでに減速し、マップが大きくなるほどブレーキが強くなります。 マップ上のユニットの種類の数ではありません。 ポイントは、表示されるマップタイルの数です! ゲームワールドの各セルのランドスケープでは、近くのすべての山、川、海、表面の種類(砂、草、雪)が考慮されるため、いくつかの隣接するセルがランドスケープの各セルに影響するため、可能なすべてのタイルの幾何学的構成を事前に準備することは困難です。
しかし、マップ上のセルと同じくらい多くのこれらの異なる構成が実際にあるとは信じていませんでした;一般的なものは、少なくとも水の中と平原で見つけられなければなりませんでした。 当然、ソースコードがなければ、この分類を正しく行うことができなかったため、これらすべてのバッファーをハッシュすることにしました。 状況は、新しいタイルが定期的に生成されたという事実によってさらに複雑になりました。 ハッシュは十分に速いはずです。
ハッシュとして、元のバイトを数字として、ベース5の頂点バッファーデータ表現を選択しました。 5は2の累乗ではなく素数であり、プロセッサでの5による乗算はLEA EAX [EAX * 4 + EAX]として実装されているため、これは衝突なしに入力をうまく混合しました。 ハッシュ自体は、同じUnlock()頂点バッファーに固定されていました。 ロックを解除すると、ハッシュされたキャッシュで同一の頂点バッファーが検索され、もしそうであれば、ロックされていないバッファーデータが削除され、civ4.exeで使用したIDirect3DVertexBufferの代わりにDrawIndexedPrimitiveを使用して描画した場合、キャッシュされたデータを含む実際のバッファーを使用しました。 。
半日、アセンブラーの挿入と迂回フックを介したCOM呼び出しのインターセプト(何らかの理由でvtblパッチが機能しませんでした)を組み合わせたC ++テンプレートからこのスナッグを書きました。 -メモリ消費量が800Mbから300-400Mbに減少しました! 2010年の基準では、これはメモリ消費を4ギガバイトから1.5ギガバイトに削減することに相当します。
ワイルドスワップのために、各ターン中にお茶を飲み終えることなく、始めたゲームをやっと完成させることができました。 私はcivfanatics.comにパッチを投稿しました、人々は喜んでいます(
リンク )。 初期の頃は市民ファンのみで15万件のダウンロードがあり、このパッチはさまざまなファンサイトに再投稿されました。 そのとき私が経験したことは言葉で説明するのは難しいです...それは話題でした! 最終的に、sorsaを使用せずにこの重大な問題を修正することができました。これも些細なメモリリークではなく、重大なアーキテクチャ上の欠陥です。 Firaxisは、独自のコードを手元に置いていても、この動きを数か月間繰り返すことはできませんでした。 ラメコーディングのパブリックフラギングは成功しました。
PS:Civilization 5の状況は見ていません。
PPS:StarCraft 2が合格している間、グラフィック/メモリ消費量をCrysisと比較することを余儀なくされました。 幸いなことに、私の睡眠と、おそらくBlizzardの場合、ラップトップには4GbのRAMが搭載されていました。 したがって、SC2メモリーの不当な食い込みは、スワップではなく困惑していました。 彼が2Gbの鉄片で私を捕まえたなら、私は古いものを引き継がなければなりません:)