0

私は現在、複素数の算術演算を大量に必要とするC#フラクタルジェネレータープロジェクトに取り組んでおり、数学を高速化する方法を考えています。TestNumericsComplex以下は、、、、TestCustomComplexおよびで示されている3つのデータストレージ方法のいずれかを使用してマンデルブロ計算の速度をテストする簡略化されたコードセットですTestPairedDoubles。マンデルブロは単なる例であり、将来の開発者がプラグインフラクタル式を作成できるようにする予定であることを理解してください。

基本的に、使用することSystem.Numerics.Complexは問題ないアイデアですが、doubleのペアまたはカスタムのComplex構造体を使用することは無難なアイデアです。GPUを使用して算術演算を実行できますが、それによって移植性が制限または中断されませんか?内側のループ(i、x、y)の順序を変えてみましたが無駄になりました。内側のループを高速化するために他に何ができますか?ページフォールトの問題が発生していますか?固定小数点数システムを使用すると、浮動小数点値とは対照的に、速度が向上しますか?

私はすでにParallel.ForC#4.0で知っています。わかりやすくするために、コードサンプルからは省略しています。また、C#は通常、高性能に適した言語ではないことも認識しています。プラグインにはReflectionを、ウィンドウ処理にはWPFを利用するためにC#を使用しています。

using System;
using System.Diagnostics;

namespace SpeedTest {
class Program {
    private const int ITER = 512;
    private const int XL = 1280, YL = 1024;

    static void Main(string[] args) {
        var timer = new Stopwatch();
        timer.Start();
        //TODO use one of these two lines
        //TestCustomComplex();
        //TestNumericsComplex();
        //TestPairedDoubles();
        timer.Stop();
        Console.WriteLine(timer.ElapsedMilliseconds);
        Console.ReadKey();
    }

    /// <summary>
    /// ~14000 ms on my machine
    /// </summary>
    static void TestNumericsComplex() {
        var vals = new System.Numerics.Complex[XL,YL];
        var loc = new System.Numerics.Complex[XL,YL];

        for (int x = 0; x < XL; x++) for (int y = 0; y < YL; y++) {
            loc[x, y] = new System.Numerics.Complex((x - XL/2)/256.0, (y - YL/2)/256.0);
            vals[x, y] = new System.Numerics.Complex(0, 0);
        }

        for (int i = 0; i < ITER; i++) {
            for (int x = 0; x < XL; x++)
            for (int y = 0; y < YL; y++) {
                if(vals[x,y].Real>4) continue;
                vals[x, y] = vals[x, y] * vals[x, y] + loc[x, y];
            }
        }
    }


    /// <summary>
    /// ~17000 on my machine
    /// </summary>
    static void TestPairedDoubles() {
        var vals = new double[XL, YL, 2];
        var loc = new double[XL, YL, 2];

        for (int x = 0; x < XL; x++) for (int y = 0; y < YL; y++) {
                loc[x, y, 0] = (x - XL / 2) / 256.0;
                loc[x, y, 1] = (y - YL / 2) / 256.0;
                vals[x, y, 0] = 0;
                vals[x, y, 1] = 0;
            }

        for (int i = 0; i < ITER; i++) {
            for (int x = 0; x < XL; x++)
                for (int y = 0; y < YL; y++) {
                    if (vals[x, y, 0] > 4) continue;
                    var a = vals[x, y, 0] * vals[x, y, 0] - vals[x, y, 1] * vals[x, y, 1];
                    var b = vals[x, y, 0] * vals[x, y, 1] * 2;
                    vals[x, y, 0] = a + loc[x, y, 0];
                    vals[x, y, 1] = b + loc[x, y, 1];
                }
        }
    }


    /// <summary>
    /// ~16900 ms on my machine
    /// </summary>
    static void TestCustomComplex() {
        var vals = new Complex[XL, YL];
        var loc = new Complex[XL, YL];

        for (int x = 0; x < XL; x++) for (int y = 0; y < YL; y++) {
            loc[x, y] = new Complex((x - XL / 2) / 256.0, (y - YL / 2) / 256.0);
            vals[x, y] = new Complex(0, 0);
        }

        for (int i = 0; i < ITER; i++) {
            for (int x = 0; x < XL; x++)
            for (int y = 0; y < YL; y++) {
                if (vals[x, y].Real > 4) continue;
                vals[x, y] = vals[x, y] * vals[x, y] + loc[x, y];
            }
        }
    }

}

public struct Complex {
    public double Real, Imaginary;
    public Complex(double a, double b) {
        Real = a;
        Imaginary = b;
    }
    public static Complex operator + (Complex a, Complex b) {
        return new Complex(a.Real + b.Real, a.Imaginary + b.Imaginary);
    }
    public static Complex operator * (Complex a, Complex b) {
        return new Complex(a.Real*b.Real - a.Imaginary*b.Imaginary, a.Real*b.Imaginary + a.Imaginary*b.Real);
    }
}

}

編集

GPUが唯一の実行可能なソリューションのようです。C / C ++との相互運用性は無視します。これは、将来のプラグインで相互運用性を強制するほどのスピードアップが重要であるとは思わないためです。

利用可能なGPUオプション(私は実際にしばらくの間調べてきました)を調べた後、私はついに私が信じているものが優れた妥協点であることに気づきました。私は、プログラムがリリースされるまでにほとんどのデバイスが標準をサポートすることを期待して、OpenCLを選択しました。OpenCLTemplateは、 clooを使用して、.Net(アプリケーションロジック用)と "OpenCL C99"(並列コード用)の間のわかりやすいインターフェイスを提供します。プラグインには、統合を容易にするためのSystem.Numerics.Complexによる標準実装に加えて、ハードウェアアクセラレーション用のOpenCLカーネルを含めることができます。

OpenCL C99コードの記述に関する利用可能なチュートリアルの数は、標準がプロセッサベンダーに採用されるにつれて、急速に増えると予想しています。これにより、プラグイン開発者がオプションを利用することを選択した場合に、適切に定式化された言語を提供しながら、プラグイン開発者にGPUコーディングを適用する必要がなくなります。また、コードはOpenCLを介して直接変換されるため、IronPythonスクリプトはコンパイル時まで不明であるにもかかわらずGPUアクセラレーションに同等にアクセスできることを意味します。

GPUアクセラレーションを.Netプロジェクトと統合することに興味がある将来の人には、OpenCLTemplateを強くお勧めします。OpenCLC99を学習することには認められたオーバーヘッドがあります。ただし、代替APIを学習するよりも少し難しいだけであり、例や一般的なコミュニティからのサポートが向上する可能性があります。

4

3 に答える 3

2

あなたの最善の策は、これらの計算をグラフィックカードにロードすることを検討することだと思います。この種のことにグラフィックカードを使用できるopenCLと、openGLシェーダーを使用できるopenCLがあります。

これを実際に活用するには、並行して計算する必要があります。100万の数を平方根にしたいとしましょう(私は知っていますが、原理は同じです)。CPUでは、一度に1つしか実行できないか、コアの数を計算し、たとえば8コアと予想して、それぞれがデータのサブセットに対して計算を実行するようにします。

たとえば、計算をグラフィックカードにオフロードする場合は、たとえば、空間内の1/4百万の3Dポイント(頂点ごとに4つのフロート)のようにデータを「フィード」してから、頂点シェーダーに正方形を計算させます。各頂点の各xyzwのルート。グラフィックカードには、はるかに多くのコアがあります。たとえ100であったとしても、CPUよりもはるかに多くの数を一度に処理できます。

シェーダーの使用は期待していませんが、必要に応じて、さらに情報を追加してこれを具体化することができますが、とにかくそれらをスクラッチする必要があります。

編集

この比較的安価なカードを見ると、nvidea GT 220には、48個の「CUDA」コアがあることがわかります。これらは、openCLやシェーダーなどを使用するときに使用しているものです。

編集2

さて、GPUアクセラレーションの使用にかなり興味があるようです。openCLの使用については説明できませんが、調べたことはありませんが、実際のグラフィックアプリケーションがなくても、シェーダーを使用するopenGL/DirectXアプリケーションとほぼ同じように機能すると思います。DirectXの方法について説明します。これは私が知っていることですが(ほぼ)、私の理解では、openGLの場合はほぼ同じです。

まず、ウィンドウを作成する必要があります。クロスプラットフォームが必要な場合、GLUTはおそらく最善の方法であり、世界で最高のライブラリではありませんが、ウィンドウを素晴らしく高速に提供します。実際にはレンダリングを表示しないので、タイトルを「ハードウェアアクセラレーション」のようなものに設定するのに十分な大きさの小さなウィンドウにすることができます。

グラフィックカードをセットアップしてレンダリングする準備ができたら、ここからチュートリアルに従ってこの段階に進みます。これにより、3Dモデルを作成し、画面上でそれらを「アニメーション化」できる段階に到達します。

次に、入力データを入力する頂点バッファーを作成します。頂点は通常、3つ(または4つ)のフロートになります。あなたの価値観がすべて独立しているなら、それは素晴らしいことです。ただし、それらをグループ化する必要がある場合、たとえば、実際に2Dベクトルを使用している場合は、データを正しく「パック」する必要があります。たとえば、2Dベクトルを使用して計算を行い、openGLが3Dベクトルを処理している場合、vector.xとvector.yが実際の入力ベクトルであり、vector.zは単なる予備データになります。

ご覧のとおり、ベクトルシェーダーは、一度に1つのベクトルしか処理できず、入力として複数のベクトルを表示することはできません。より大きなデータセットを表示できるジオメトリシェーダーを使用して調べることができます。

そうです、頂点バッファを設定し、それをグラフィックカードの上にポップします。また、「頂点シェーダー」を作成する必要があります。これは、いくつかの数学を実行できるCのような言語のテキストファイルです。これは完全なC実装の考え方ではありませんが、自分が何をしているのかを知るにはCのように見えます。openGLシェーダーの正確なインとアウトは私を超えていますが、簡単なチュートリアルを見つけるのは簡単だと確信しています。

あなたが自分でしていることの1つは、頂点シェーダーの出力を2番目のバッファー(事実上あなたの出力)に送るためにどれだけ正確に取得できるかを見つけることです。頂点シェーダーは、設定したバッファー内の頂点データを変更しません。つまり、(シェーダーに関する限り)一定ですが、シェーダーを2番目のバッファーに出力させることができます。

計算は次のようになります

createvertexbuffer()
loadShader("path to shader code", vertexshader) // something like this I think
// begin 'rendering'
setShader(myvertexshader)
setvertexbuffer(myvertexbuffer)
drawpoints() // will now 'draw' your points
readoutputbuffer()

これがお役に立てば幸いです。私が言ったように、私はまだこれを学んでいます、そしてそれでも私は物事のDirectXの方法を学んでいます。

于 2011-02-16T00:25:14.443 に答える
0

カスタム構造体を変更可能にすると、30% 向上しました。これにより、呼び出しとメモリ使用量が削減されます

//instead of writing  (in TestCustomComplex())
vals[x, y] = vals[x, y] * vals[x, y] + loc[x, y];

//use
vals[x,y].MutableMultiAdd(loc[x,y]);

//defined in the struct as
public void MutableMultiAdd(Complex other)
    {
        var tempReal = (Real * Real - Imaginary * Imaginary) + other.Real;
        Imaginary =( Real * Imaginary + Imaginary * Real )+ other.Imaginary;
        Real = tempReal;
    }

Matrix Multiply では、「Unsafe { Fixed(){}}」を使用して配列にアクセスすることもできます。これを使用すると、TestCustomComplex() が 15% 向上しました。

private static void TestCustomComplex()
    {
        var vals = new Complex[XL, YL];
        var loc = new Complex[XL, YL];

        for (int x = 0; x < XL; x++)
            for (int y = 0; y < YL; y++)
            {
                loc[x, y] = new Complex((x - XL / 2) / 256.0, (y - YL / 2) / 256.0);
                vals[x, y] = new Complex(0, 0);
            }

        unsafe
        {
            fixed (Complex* p = vals, l = loc)
            {
                for (int i = 0; i < ITER; i++)
                {
                    for (int z = 0; z < XL*YL; z++)
                    {
                        if (p[z].Real > 4) continue;
                        p[z] = p[z] * p[z] + l[z];
                    }
                }
            }
        }
    }
于 2012-06-19T15:03:05.530 に答える
-1

個人的には、これが大きな問題である場合は、C++ dll を作成し、それを使用して演算を行います。このプラグインは C# から呼び出すことができるため、WPF やリフレクションなどを引き続き利用できます。

注意すべきことの 1 つは、プラグインの呼び出しは必ずしも「高速」ではないため、すべてのデータを一度に渡し、あまり頻繁に呼び出さないようにすることです。

于 2011-02-16T00:07:09.553 に答える