我现在正在处理一个 C# Fractal Generator 项目,该项目需要大量的复数算术,我正在尝试想办法加快数学运算速度。下面是一组简化的代码,它使用三种数据存储方法中的一种来测试 Mandelbrot 计算的速度,如TestNumericsComplex
、TestCustomComplex
和所示TestPairedDoubles
。请理解 Mandelbrot 只是一个示例,我打算让未来的开发人员能够创建插件分形公式。
基本上我认为 usingSystem.Numerics.Complex
是一个不错的想法,而使用一对双打或自定义 Complex 结构是可以接受的想法。我可以使用 gpu 执行算术,但这不会限制或破坏可移植性吗?我尝试改变内部循环的顺序(i,x,y)无济于事。我还能做些什么来帮助加快内部循环?我遇到页面错误问题了吗?与浮点值相比,使用定点数系统会提高我的速度吗?
我已经Parallel.For
在 C# 4.0 中知道了;为了清楚起见,我的代码示例中省略了它。我也知道 C# 通常不是高性能的好语言。我使用 C# 来利用反射插件和 WPF 窗口。
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”(用于并行代码)之间提供易于理解的接口。插件可以包括用于硬件加速的 OpenCL 内核以及带有 System.Numerics.Complex 的标准实现,以便于集成。
随着标准被处理器供应商采用,我预计有关编写 OpenCL C99 代码的可用教程的数量会迅速增长。这使我无需对插件开发人员强制执行 GPU 编码,同时为他们提供精心设计的语言(如果他们选择利用该选项)。这也意味着 IronPython 脚本将具有同等的 GPU 加速访问权限,尽管直到编译时才知道,因为代码将直接通过 OpenCL 进行转换。
对于将来有兴趣将 GPU 加速与 .Net 项目集成的任何人,我强烈推荐 OpenCLTemplate。学习 OpenCL C99 有一定的开销。但是,它仅比学习替代 API 稍微困难一些,并且可能会从示例和一般社区获得更好的支持。