Ome*_*mer 9 c# optimization gpu fractals mandelbrot
首先,我知道这个问题听起来好像我没有搜索,但我做了很多.
我为C#编写了一个小的Mandelbrot绘图代码,它基本上是一个带有PictureBox的窗体,我在其上绘制了Mandelbrot集.
我的问题是,它是非常慢的.如果没有深度变焦,它可以很好地工作并且移动并且变焦非常平滑,每次绘制只需不到一秒钟,但是一旦我开始放大一点并到达需要更多计算的地方,它就变得非常慢.
在其他Mandelbrot应用程序上,我的计算机在我的应用程序中工作得慢得多的地方确实很好,所以我猜我可以做很多事情来提高速度.
我做了以下事情来优化它:
我没有在位图对象上使用SetPixel GetPixel方法,而是使用LockBits方法直接写入内存,这使得事情变得更快.
我没有使用复数对象(我自己创建的类,而不是内置的类),而是使用2个变量re和im模拟复数.这样做可以减少乘法,因为在计算过程中对实部和虚部进行平方是一些事情,所以我只需将方块保存在变量中并重复使用结果而无需重新计算.
我使用4个线程绘制Mandelbrot,每个线程执行不同的图像四分之一,它们都同时工作.据我所知,这意味着我的CPU将使用其4个核心来绘制图像.
我使用Escape Time算法,据我所知最快?
这是我如何在像素之间移动并计算,它被注释掉,所以我希望它是可以理解的:
//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;
}
}
Run Code Online (Sandbox Code Playgroud)
我该怎么做才能改善这一点?您在我的代码中发现任何明显的优化问题吗?
现在有2种方法我知道我可以改进它:
我需要为数字使用不同的类型,double是精确限制的,我敢肯定有更好的非内置替代类型更快(它们繁殖和添加更快)并且具有更高的准确性,我只需要有人来指出我需要看的地方并告诉我它是否属实.
我可以将处理转移到GPU.我不知道如何做到这一点(也许是OpenGL?DirectX?它甚至是那么简单还是我需要学习很多东西?).如果有人可以给我发送关于这个主题的正确教程的链接,或者告诉我一般情况下这将是很好的.
非常感谢您阅读,希望您能帮助我:)
如果决定将处理移至GPU,则可以从多个选项中进行选择。由于您使用的是C#,因此XNA将允许您使用HLSL。如果选择此选项,RB Whitaker将提供最简单的XNA教程。另一种选择是OpenCL。OpenTK随附了朱莉娅集合分形的演示程序。修改以显示mandlebrot集非常简单。在这里看到, 只需记住找到与源代码一起提供的GLSL着色器。
关于GPU,示例对我无济于事,因为我对此主题一无所知,它甚至如何工作,GPU可以进行哪种计算(甚至无法访问?)。
不同的GPU软件的工作原理不同,但是...
通常,程序员将使用着色器语言(例如HLSL,GLSL或OpenCL)为GPU编写程序。用C#编写的程序将加载着色器代码并进行编译,然后使用API中的函数将作业发送到GPU,然后将结果返回。
如果您想对着色器进行一些练习而不必担心API,请看看FX Composer或渲染猴子。
如果使用的是HLSL,则渲染管道如下所示。

顶点着色器负责在3D空间中获取点并计算其在2D视场中的位置。(因为您是在2D模式下工作,所以对您来说并不是什么大问题)
顶点着色器完成后,像素着色器负责将着色器效果应用于像素。
OpenCL是另一回事,它面向通用GPU计算(即,不仅是图形)。它功能更强大,可用于GPU,DSP和构建超级计算机。
为了将处理转移到 GPU,这里有很多优秀的例子:
https://www.shadertoy.com/results?query=mandelbrot
请注意,您需要支持 WebGL 的浏览器才能查看该链接。在 Chrome 中效果最佳。
我不是分形专家,但您似乎已经在优化方面取得了很大进展。超出这个范围可能会使代码更难以阅读和维护,因此您应该问自己这是值得的。
我在其他分形程序中经常观察到的一项技术是:在缩放时,以较低的分辨率计算分形,并在渲染期间将其拉伸到完整尺寸。然后在缩放停止后立即以全分辨率渲染。
另一个建议是,当您使用多个线程时,您应该注意每个线程不要读/写其他线程的内存,因为这会导致缓存冲突并损害性能。一种好的算法可以将工作分成扫描线(而不是像现在这样分成四个季度)。创建多个线程,然后只要还有待处理的行,就将扫描线分配给可用的线程。让每个线程将像素数据写入本地内存,并在每行之后将其复制回主位图(以避免缓存冲突)。