2017-07-21 12 views
20

概況C++で割り当てられたメモリをコミットする方法は?

帯域幅、CPUの使用率、およびGPUの使用率の両方に非常に集中的にアプリケーションを別のGPUからおよそ10〜15ギガバイト毎秒を転送する必要があります。 DX11 APIを使用してGPUにアクセスしているため、GPUへのアップロードは、1回のアップロードごとにマッピングが必要なバッファでのみ実行できます。アップロードは一度に25MBのチャンクで行われ、16スレッドはマップバッファにバッファを同時に書き込んでいます。これについては何もできません。次のバグがなければ、書き込みの実際の同時実行レベルは低くなければなりません。

3つのPascal GPU、ハイエンドのHaswellプロセッサー、4チャネルRAMを搭載した堅牢なワークステーションです。ハードウェア上の改善はあまりありません。これは、Windowsのデスクトップ版を実行している10

実際の問題

私が合格したら〜50%のCPU負荷、にマップされたメモリにアクセスする際に呼び出されるWindowsカーネル、内部MmPageFault()で何か(あなたアドレス空間はまだOSによってコミットされていませんでした)はひどく壊れ、残りの50%のCPU負荷はMmPageFault()のスピンロックで無駄になっています。 CPUが100%活用され、アプリケーションのパフォーマンスが完全に低下します。

これは、毎秒プロセスに割り当てる必要があり、DX11バッファがマップされていないときにプロセスから完全にマップされていない膨大な量のメモリが原因であると考えます。それに対応して、実際にはMmPageFault()への呼び出しは何千回も起こりますが、これは連続的に発生します。memcpy()は順次バッファに書き込まれます。遭遇した個々のコミットされていないページ。

CPU負荷が50%を超えると、ページ管理を保護するWindowsカーネルの楽観的なスピンロックによって、パフォーマンスが完全に低下します。

考慮事項

バッファはDX11ドライバにより割り当てられます。割り当て戦略については何も調整できません。異なるメモリAPIの使用、特に再利用は不可能です。

DX11 APIの呼び出し(バッファのマッピング/アンマップ)はすべて1つのスレッドから発生します。実際のコピー操作は、システム内の仮想プロセッサより多くのスレッドでマルチスレッド化される可能性があります。

メモリ帯域幅の要件を減らすことはできません。これはリアルタイムアプリケーションです。実際、ハード制限は現在、プライマリGPUのPCIe 3.0 16x帯域幅です。私ができるならば、私はすでにさらに進める必要があります。

マルチスレッドコピーを避けることはできません。独立したプロデューサ - コンシューマキューがあり、それらは簡単にはマージできません。

スピンロックのパフォーマンスの低下は非常に稀です(ユースケースではそれを押しているため)、Googleではスピンロック機能の名前には1つの結果が見つかりません。

マッピングをより詳細に制御するAPI(Vulkan)へのアップグレードは進行中ですが、短期的な修正には適していません。現在のところ、より良いOSカーネルに切り替えることは、同じ理由からオプションではありません。

CPU負荷を軽減することもできません。 (通常は些細で安価な)バッファコピー以外の作業が必要な作業が多すぎます。

質問

何ができますか?

個々のページフォールトの数を大幅に減らす必要があります。私は自分のプロセスにマップされているバッファのアドレスとサイズを知っており、メモリがまだコミットされていないことも知っています。

最小限のトランザクションでメモリをコミットする方法を教えてください。

アンマップ後のバッファの割り当て解除を防止するDX11のエキゾチックなフラグ、単一のトランザクションでコミットを強制するWindows API、ほとんど何でも歓迎します。

現在の状態

// In the processing threads 
{ 
    DX11DeferredContext->Map(..., &buffer) 
    std::memcpy(buffer, source, size); 
    DX11DeferredContext->Unmap(...); 
} 
+1

あなたはすべての16スレッドすべてをまとめて約400Mのように聞こえます。かなり低い。アプリケーションでこれを超えないことを確認できますか?ピックメモリ消費量は何ですか?私はあなたがメモリリークを持っているのだろうかと思います。 – Serge

+0

ピーク消費量は約7-8GBですが、すべての種類のボトルネックを補うためには、処理パイプライン全体が合計で1秒以上のバッファリングを必要とすることを考慮すると、正常です。はい、それは「唯一の」400MB、毎秒25回です。そして、基本的なCPU負荷が50%を超え、スピンロックのパフォーマンスが完全にCPU使用率の0〜40-50%に急上昇するまでうまく動作します。同時にシステム上の他のプロセスにも影響を与えます。 – Ext3h

+1

1.あなたの物理的な記憶は何ですか?他のアクティブなプロセスをすべて終了できますか? 2.あなたが50%のしきい値を見ているので#2を推測すると、ハイパースレッディングに関するいくつかの問題が発生する可能性があります。物理コアの数はいくつですか? 8?ハイパースレッディングを無効にすることはできますか?クリーンなマシン上で実際のCPUと同じ数のスレッドを実行してみてください。 – Serge

答えて

11

現在の回避策、簡略化された擬似コード:

// During startup 
{ 
    SetProcessWorkingSetSize(GetCurrentProcess(), 2*1024*1024*1024, -1); 
} 
// In the DX11 render loop thread 
{ 
    DX11context->Map(..., &resource) 
    VirtualLock(resource.pData, resource.size); 
    notify(); 
    wait(); 
    DX11context->Unmap(...); 
} 
// In the processing threads 
{ 
    wait(); 
    std::memcpy(buffer, source, size); 
    signal(); 
} 

VirtualLock()すぐにRAMを使用して、指定したアドレスの範囲をバックアップするために、カーネルを強制します。補完するVirtualUnlock()関数への呼び出しはオプションです。アドレス範囲がプロセスからマップされていないときは暗黙的に(しかも追加コストなしで)発生します。 (明示的に呼ばれる場合、それはロックのコストの約1/3がかかります。)

VirtualLock()が全く動作するためには、VirtualLock()によってロックされたすべてのメモリ領域の合計が超えることができないとしてSetProcessWorkingSetSize()が、最初に呼び出される必要がありますプロセスに設定されている最小ワーキングセットサイズ。 "最小"ワーキングセットサイズをプロセスのベースラインメモリフットプリントよりも高いものに設定することは、システムが実際に潜在的にスワップしない限り副作用がなく、プロセスは実際のワーキングセットサイズよりも多くのRAMを消費しません。


ただ、個々のスレッドでVirtualLock()の使用、およびいえMap/Unmap呼び出しのために延期DX11コンテキストを使用するには、即座にもう少し許容15%40から50パーセントからパフォーマンスペナルティを減少させませんでした。遅延コンテキストの使用を廃棄

、及び排他すべてのソフト・フォールト、並びに対応する割り当て解除シングルスレッドでをマップ解除の両方を誘発する、必要なパフォーマンスの向上が得られました。そのスピンロックの総コストは今合計CPU使用量の1%である<にまで下がりました。


要約?

Windowsでソフトフォールトが発生することが予想される場合は、すべて同じスレッド内に保つことができます。並列のmemcpy自体を実行することは問題ではなく、場合によってはメモリ帯域幅を十分に活用する必要もあります。しかし、それはメモリがすでにRAMにコミットされている場合のみです。 VirtualLock()はそれを保証する最も効率的な方法です。

(あなたのプロセスにメモリをマップするのDirectXのようなAPIを使用して作業している場合を除き、あなただけの標準C++ newmallocあなたのメモリは、プロセス内でプールし、再利用されるで作業している場合、あなたは。頻繁にコミットされていないメモリに遭遇する可能性は低いですとにかく、ソフトフォールトはまれです。)

Windowsで作業するときは、どのような並行ページフォールトも避けるようにしてください。

関連する問題