【发布时间】:2011-02-16 00:01:30
【问题描述】:
我现在正在处理一个 C# Fractal Generator 项目,该项目需要大量的复数算术,我正在尝试想办法加快数学运算速度。下面是一组简化的代码,它使用TestNumericsComplex、TestCustomComplex 和TestPairedDoubles 中所示的三种数据存储方法之一来测试 Mandelbrot 计算的速度。请理解,Mandelbrot 只是一个示例,我希望未来的开发人员能够创建插件分形公式。
基本上我看到使用System.Numerics.Complex 是一个不错的主意,而使用一对双打或自定义的复杂结构是可以接受的主意。我可以使用 gpu 执行算术,但这不会限制或破坏可移植性吗?我尝试改变内部循环的顺序(i,x,y)无济于事。我还能做些什么来帮助加快内部循环?我遇到页面错误问题了吗?与浮点值相比,使用定点数系统会提高我的速度吗?
我已经知道 C# 4.0 中的 Parallel.For;为了清楚起见,我的代码示例中省略了它。我也知道 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 稍微困难一些,并且可能会从示例和一般社区获得更好的支持。
【问题讨论】:
-
“我也知道 C# 通常不是高性能的好语言”——这是不正确的。
-
您将无法加速单个复杂的加法/乘法。相反,您需要利用更大的计算,可能将一系列计算流水线化到 GPU
-
@Mitch 我认为他的意思是,你可以通过一些 C# 中没有的花哨的聪明来使 C++ 代码比 C# 更快。
-
“不正确”wisegeek.com/… 发表此类言论时,请提供链接或事实。
-
@cyberkiwi:大概你的意思是也适用于海报。此外,这是一个自愿网站;你去找链接怎么样?
标签: c#-4.0 performance fractals complex-numbers