1

私は現在、趣味のプロジェクトとしてC#のレイトレーサーに取り組んでいます。私はc++実装からいくつかのトリックを実装することによってまともなレンダリング速度を達成しようとしていますが、問題が発生しています。

レイトレーサーがレンダリングするシーン内のオブジェクトはKdTree構造に格納され、ツリーのノードは配列に格納されます。私が問題を抱えている最適化は、できるだけ多くのツリーノードをキャッシュラインに収めようとしているときです。これを行う1つの方法は、ノードに左側の子ノードへのポインターのみを含めることです。その場合、配列内の左側の子の直後に右側の子が続くことは暗黙的です。

ノードは構造体であり、ツリーの構築中に、静的メモリマネージャークラスによって配列に正常に配置されます。私が木を横断し始めると、最初はうまく機能しているように見えます。次に、レンダリングの初期の時点(毎回ほぼ同じ場所)で、ルートノードの左側の子ポインターが突然ヌルポインターを指しています。配列がヒープ上にあるため、ガベージコレクターが構造体を移動したという結論に達しました。

アドレスをメモリに固定するためにいくつかのことを試みましたが、必要に応じて、アプリケーションの存続期間全体にわたってそれらのどれも持続しないようです。'fixed'キーワードは、単一のメソッド呼び出し中にのみ役立つようであり、'fixed'配列の宣言は、ノードがそうではない単純な型でのみ実行できます。これを行うための良い方法はありますか、それともC#が意図されていなかったものの道をはるかに下っていますか?

ところで、c ++に変更することは、おそらく高性能プログラムにはより良い選択ですが、オプションではありません。

4

5 に答える 5

4

まず、C#を通常使用している場合、ガベージコレクターもすべての参照を更新するため、ガベージコレクターが移動するために、突然null参照を取得することはできません。したがって、ガベージコレクターが移動することを心配する必要はありません。

物事をメモリに固定することはできますが、これは解決するよりも多くの問題を引き起こす可能性があります。一つには、ガベージコレクターがメモリを適切に圧縮するのを妨げ、そのようにパフォーマンスに影響を与える可能性があります。

あなたの投稿から私が言えることの1つは、構造体を使用しても期待どおりのパフォーマンスが得られない可能性があるということです。C#は、構造体を含むメソッド呼び出しのインライン化に失敗します。最新のランタイムベータでこれを修正したとしても、構造体は頻繁にうまく機能しません。

個人的には、このようなC ++のトリックは、一般的にC#にうまく引き継がれる傾向はないと思います。あなたは少し手放すことを学ぶ必要があるかもしれません。パフォーマンスを改善する他のより微妙な方法があるかもしれません;)

于 2008-09-01T19:14:11.233 に答える
2

静的メモリマネージャは実際に何をしていますか?安全でないこと(P / Invoke、安全でないコード)を実行していない限り、表示される動作はプログラムのバグであり、CLRの動作によるものではありません。

第二に、構造間のリンクに関して、「ポインタ」とはどういう意味ですか?文字通り、安全でないKdTree *ポインターを意味しますか?そうしないでください。代わりに、配列へのインデックスを使用してください。1つのツリーのすべてのノードが同じ配列に格納されることを期待しているため、配列への個別の参照は必要ありません。単一のインデックスで十分です。

最後に、本当にKdTree *ポインターを使用する必要がある場合は、静的メモリマネージャーは、Marshal.AllocHGlobalまたは別のアンマネージメモリソースなどを使用して大きなブロックを割り当てる必要があります。この大きなブロックをKdTree配列として処理する(つまり、KdTree * Cスタイルのインデックスを作成する)必要があります。また、「フリー」ポインターをバンプすることにより、この配列からノードをサブ割り当てする必要があります。

この配列のサイズを変更する必要がある場合は、もちろん、すべてのポインターを更新する必要があります。

ここでの基本的な教訓は、安全でないポインタと管理対象メモリが「固定」ブロックの外で混ざらないことです。もちろん、スタックフレームアフィニティがあります(つまり、関数が戻ると、固定された動作がなくなります)。GCHandle.Alloc(yourArray、GCHandleType.Pinned)を使用して、配列などの任意のオブジェクトを固定する方法がありますが、ほとんどの場合、そのルートをたどりたくはありません。

自分がしていることをより詳細に説明すると、より賢明な答えが得られます。

于 2008-09-01T21:16:14.810 に答える
1

本当にこれを行いたい場合は、GCHandle.Alloc メソッドを使用して、fixed ステートメントのようにスコープの最後で自動的に解放されずにポインターを固定するように指定できます。

しかし、他の人が言っているように、これを行うことはガベージコレクターに過度の圧力をかけています. ノードのペアを保持する構造体を作成し、ノードの配列ではなく NodePair の配列を管理するだけではどうですか?

メモリのチャンクへの完全に管理されていないアクセスが本当に必要な場合は、マネージド ヒープの一部を永続的に固定するよりも、アンマネージド ヒープから直接メモリを割り当てたほうがよいでしょう (これにより、ヒープが適切にアクセスできなくなります)。コンパクト自体)。これをすばやく簡単に行う方法の 1 つは、Marshal.AllocHGlobal メソッドを使用することです。

于 2008-09-01T23:25:33.670 に答える
0

配列参照とインデックスのペアを格納することは本当に禁止されていますか?

于 2008-09-01T19:08:46.283 に答える
0

あなたの静的メモリマネージャーは実際に何をしていますか? 安全でないこと (P/Invoke、安全でないコード) を実行していない限り、表示されている動作はプログラムのバグであり、CLR の動作によるものではありません。

実際、私は安全でないポインターについて話していました。私が欲しかったのは のようなものMarshal.AllocHGlobalでしたが、寿命は単一のメソッド呼び出しを超えていました。振り返ってみると、C++ コードを模倣することに夢中になりすぎた可能性があるため、インデックスを使用するだけが正しい解決策のようです。

あなたの投稿から私が言いたいことの1つは、構造体を使用してもパフォーマンスが期待どおりに向上しない可能性があるということです。C# は、構造体を含むメソッド呼び出しをインライン化できず、最新のランタイム ベータ版でこれを修正したにもかかわらず、構造体がうまく機能しないことがよくあります。

これを少し調べたところ、.NET 3.5SP1 で修正されていることがわかりました。それがランタイム ベータと呼ばれていたものだと思います。実際、この変更によってレンダリング速度が 2 倍になったことがわかりました。現在、構造体は積極的にインライン化されており、X86 システムでのパフォーマンスが大幅に向上しています (X64 は事前に構造体のパフォーマンスが優れていました)。

于 2008-09-01T23:08:27.673 に答える