9

まず第一に、この質問はまるで私が検索していないかのように聞こえることを認識していますが、私はたくさん検索しました。

私は C# 用の小さなマンデルブロー描画コードを書きました。これは基本的に、マンデルブロー セットを描画する PictureBox を備えた Windows フォームです。

私の問題は、それがかなり遅いということです。深いズームがなければ、かなりうまく機能し、移動とズームは非常にスムーズで、描画ごとに1秒もかかりませんが、少しズームインし始めて、より多くの計算が必要な場所に到達すると、非常に遅くなります.

他のマンデルブロー アプリケーションでは、アプリケーション内で動作が非常に遅い場所でも、私のコンピューターは問題なく動作するため、速度を改善するためにできることはたくさんあると思います。

最適化するために次のことを行いました。

  • ビットマップ オブジェクトで SetPixel GetPixel メソッドを使用する代わりに、LockBits メソッドを使用してメモリに直接書き込み、処理を大幅に高速化しました。

  • 複素数オブジェクト (組み込みのクラスではなく、自分で作成したクラス) を使用する代わりに、2 つの変数 re と im を使用して複素数をエミュレートしました。実数部と虚数部の 2 乗は計算中に数回行われるため、これを行うことで乗算を減らすことができました。そのため、2 乗を変数に保存し、再計算する必要なく結果を再利用するだけです。

  • 私は 4 つのスレッドを使用してマンデルブロを描画します。各スレッドは画像の異なる 4 分の 1 を処理し、すべて同時に機能します。私が理解したように、それは私のCPUが4つのコアを使用して画像を描画することを意味します.

  • Escape Time Algorithm を使用していますが、どちらが最速でしょうか?

ピクセル間を移動して計算する方法は次のとおりです。コメントアウトされているので、理解できることを願っています。

        //Pixel by pixel loop:
        for (int r = rRes; r < wTo; r++)
        {
            for (int i = iRes; i < hTo; i++)
            {

                //These calculations are to determine what complex number corresponds to the (r,i) pixel.
                double re = (r - (w/2))*step + zeroX ;
                double im = (i - (h/2))*step - zeroY;

                //Create the Z complex number
                double zRe = 0;
                double zIm = 0;

                //Variables to store the squares of the real and imaginary part.
                double multZre = 0;
                double multZim = 0;

                //Start iterating the with the complex number to determine it's escape time (mandelValue)
                int mandelValue = 0;
                while (multZre + multZim < 4 && mandelValue < iters)
                {
                    /*The new real part equals re(z)^2 - im(z)^2 + re(c), we store it in a temp variable
                    tempRe because we still need re(z) in the next calculation
                        */
                    double tempRe = multZre - multZim + re; 

                    /*The new imaginary part is equal to 2*re(z)*im(z) + im(c)
                        * Instead of multiplying these by 2 I add re(z) to itself and then multiply by im(z), which
                        * means I just do 1 multiplication instead of 2.
                        */
                    zRe += zRe; 
                    zIm = zRe * zIm + im;

                    zRe = tempRe; // We can now put the temp value in its place.

                    // Do the squaring now, they will be used in the next calculation.
                    multZre = zRe * zRe; 
                    multZim = zIm * zIm; 

                    //Increase the mandelValue by one, because the iteration is now finished.
                    mandelValue += 1;
                }


                //After the mandelValue is found, this colors its pixel accordingly (unsafe code, accesses memory directly):
                //(Unimportant for my question, I doubt the problem is with this because my code becomes really slow
                //    as the number of ITERATIONS grow, this only executes more as the number of pixels grow).
                Byte* pos = px + (i * str) + (pixelSize * r);
                byte col = (byte)((1 - ((double)mandelValue / iters)) * 255);
                pos[0] = col;
                pos[1] = col;
                pos[2] = col;

            }
        }

これを改善するにはどうすればよいですか? 私のコードに明らかな最適化の問題はありますか?

現在、私がそれを改善できることがわかっている2つの方法があります。

  1. 数値には別の型を使用する必要があります。 double は精度に制限があり、より高速 (乗算と加算が高速) で精度の高い、より優れた非組み込みの代替型があると確信しています。どこを見る必要があるかを教えて、それが本当かどうか教えてください。

  2. 処理を GPU に移動できます。これを行う方法がわかりません (OpenGL でしょうか? DirectX? そんなに単純なのか、それとも多くのことを学ぶ必要があるのでしょうか?)。誰かがこの主題に関する適切なチュートリアルへのリンクを送ってくれたり、それについて一般的に教えてくれたりしたら、それは素晴らしいことです.

ここまで読んでくれてありがとう。助けてくれることを願っている:)

4

3 に答える 3

5

処理を GPU に移行する場合は、いくつかのオプションから選択できます。C# を使用しているため、XNA では HLSL を使用できます。このオプションを選択すると、RB Whitakerの XNA チュートリアルが最も簡単になります。別のオプションはOpenCLです。OpenTKには、ジュリア セット フラクタルのデモ プログラムが付属しています。これは、mandlebrot セットを表示するように変更するのは非常に簡単です。ここを参照してください 。ソース コードに付属する GLSL シェーダーを見つけることを忘れないでください。

GPU については、このトピックについてまったくわからないため、例は役に立ちません。どのように機能し、GPU がどのような計算を実行できるか (または、どのようにアクセスされるのでしょうか?)

ただし、異なる GPU ソフトウェアの動作は異なります...

通常、プログラマーは HLSL、GLSL、OpenCL などのシェーダー言語で GPU 用のプログラムを作成します。C# で記述されたプログラムは、シェーダー コードを読み込んでコンパイルし、API で関数を使用してジョブを GPU に送信し、後で結果を取得します。

API を気にせずにシェーダーを練習したい場合は、FX Composer または render monkey をご覧ください

HLSL を使用している場合、レンダリング パイプラインは次のようになります。

パイプライン

頂点シェーダーは、3D 空間でポイントを取得し、2D ビュー フィールドでの位置を計算します。(2D で作業しているので、大きな問題ではありません)

ピクセル シェーダーは、頂点シェーダーが完了した後にシェーダー効果をピクセルに適用する役割を果たします。

OpenCL は別の話で、汎用 GPU コンピューティングを対象としています (つまり、グラフィックスだけではありません)。より強力で、GPU、DSP、およびスーパー コンピューターの構築に使用できます。

于 2013-08-13T07:18:57.663 に答える
3

GPU の WRT コーディングについては、Cudafy.Net (OpenCL も実行しますが、これは NVidia に関連付けられていません) を見て、何が起こっているのかを理解し始め、おそらくそこで必要なことをすべて実行することもできます。私はすぐにそれと私のグラフィックカードが私のニーズに合わないことに気づきましたが、あなたが今いる段階のマンデルブローにとっては問題ないはずです.

簡単に言うと、C (通常は Cuda C または OpenCL) のフレーバーを使用して GPU 用にコーディングし、「カーネル」(コンパイルされた C メソッド) を GPU にプッシュし、続いてソース データをプッシュし、その「カーネル」を呼び出すことがよくあります。使用するデータを指定するパラメータ、または結果をメモリ内のどこに配置するかを指定するいくつかのパラメータを使用します。

自分でフラクタル レンダリングを行っているときは、既に概説した理由でビットマップへの描画を避け、レ​​ンダリング フェーズを延期しました。それに加えて、私は大量のマルチスレッド コードを書く傾向がありますが、これはビットマップにアクセスしようとするのが非常に苦手です。代わりに、共通ストアに書き込みます。ごく最近では、MemoryMappedFile (組み込みの .Net クラス) を使用しました。これにより、かなりまともなランダム アクセス速度と巨大なアドレス指定可能領域が得られるからです。また、結果をキューに書き込み、データをストレージにコミットする別のスレッドを処理する傾向があります。各マンデルブロー ピクセルの計算時間は「不規則」になります。つまり、常に同じ時間がかかるとは限りません。その結果、反復回数が非常に少ない場合、ピクセル コミットがボトルネックになる可能性があります。

私は現在、マンデルブロー集合のブッダブロー視覚化で遊んでおり、GPU を使用してレンダリングをスケールアウトし (CPU で非常に長い時間がかかるため)、巨大な結果セットを取得することを検討しています。8 ギガピクセルの画像をターゲットにすることを考えていましたが、ピクセルの制約から逸脱する必要があり、精度の問題のために浮動小数点演算から離れる必要があることに気付きました。また、GPU を別の方法で操作できるように、いくつかの新しいハードウェアを購入する必要があります。異なる計算ジョブは異なる時間に終了するため (以前の反復カウントのコメントに従って)、スレッドのバッチを起動して待機することはできません。バッチ全体の中で特に高い反復回数を待つために多くの時間を無駄にすることなく、それらすべてを完了することができます。

マンデルブロー集合について作成されているのを私がめったに見ないもう 1 つのポイントは、それが対称的だということです。必要な計算の 2 倍の計算をしている可能性があります。

于 2013-08-09T13:22:43.720 に答える
1

処理を GPU に移行するために、ここに多くの優れた例があります。

https://www.shadertoy.com/results?query=mandelbrot

そのリンクを表示するには、WebGL 対応のブラウザーが必要であることに注意してください。Chrome で最適に動作します。

私はフラクタルの専門家ではありませんが、あなたはすでに最適化を行っているようです。それを超えると、コードの読み取りと保守が非常に難しくなる可能性があるため、それだけの価値があるか自問する必要があります。

他のフラクタル プログラムでよく見られるテクニックの 1 つに、ズーム中に低解像度でフラクタルを計算し、レンダリング中にフル サイズに引き伸ばすというものがあります。次に、ズームが停止したらすぐにフル解像度でレンダリングします。

もう 1 つの提案は、複数のスレッドを使用する場合、キャッシュの衝突が発生してパフォーマンスが低下するため、各スレッドが他のスレッドのメモリを読み書きしないように注意することです。良いアルゴリズムの 1 つは、作業をスキャンラインに分割することです (今のように 4 分の 1 ではなく)。多数のスレッドを作成し、処理するラインが残っている限り、使用可能なスレッドにスキャンラインを割り当てます。各スレッドがピクセル データをローカル メモリに書き込み、これを各行の後にメイン ビットマップにコピーして戻します (キャッシュの衝突を避けるため)。

于 2013-07-01T17:13:59.327 に答える