私は現在、複素数の算術演算を大量に必要とするC#フラクタルジェネレータープロジェクトに取り組んでおり、数学を高速化する方法を考えています。TestNumericsComplex
以下は、、、、TestCustomComplex
およびで示されている3つのデータストレージ方法のいずれかを使用してマンデルブロ計算の速度をテストする簡略化されたコードセットですTestPairedDoubles
。マンデルブロは単なる例であり、将来の開発者がプラグインフラクタル式を作成できるようにする予定であることを理解してください。
基本的に、使用することSystem.Numerics.Complex
は問題ないアイデアですが、doubleのペアまたはカスタムのComplex構造体を使用することは無難なアイデアです。GPUを使用して算術演算を実行できますが、それによって移植性が制限または中断されませんか?内側のループ(i、x、y)の順序を変えてみましたが無駄になりました。内側のループを高速化するために他に何ができますか?ページフォールトの問題が発生していますか?固定小数点数システムを使用すると、浮動小数点値とは対照的に、速度が向上しますか?
私はすでにParallel.For
C#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を学習するよりも少し難しいだけであり、例や一般的なコミュニティからのサポートが向上する可能性があります。