当前位置: 首页 > 知识库问答 >
问题:

使C#mandelbrot绘图更高效

须敏学
2023-03-14

首先,我意识到这个问题听起来好像我没有搜索,但我搜索了很多。

我为C#写了一个小的Mandelbrot绘图代码,它基本上是一个带有PictureBox的窗口窗体,我在上面绘制了Mandelbrot集。

我的问题是,速度很慢。如果没有深度缩放,效果会非常好,移动和缩放都非常平滑,每张图只需要不到一秒钟的时间,但是一旦我开始放大一点,到达需要更多计算的地方,速度就会变得非常慢。

在其他Mandelbrot应用程序中,我的计算机在我的应用程序中工作速度慢得多的地方做得很好,所以我想我可以做很多事情来提高速度。

我做了以下事情来优化它:

>

  • 我没有在位图对象上使用SetPixel-GetPixel方法,而是使用LockBits方法直接写入内存,这使得速度大大加快。

    我没有使用复数对象(使用我自己创建的类,而不是内置类),而是使用re和im两个变量模拟复数。这样做可以减少乘法,因为在计算过程中,实部和虚部的平方运算只需几次,所以我只需将平方保存在变量中,并重用结果,而无需重新计算。

    我用4条线来画曼德尔布罗特,每条线画出图像的四分之一,它们同时工作。据我所知,这意味着我的CPU将使用其4个内核来绘制图像。

    我使用逃逸时间算法,据我所知是最快的?

    这是我如何在像素之间移动和计算,它被注释掉,所以我希望它是可以理解的:

            //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;
    
                }
            }
    

    我能做些什么来改进这一点?您在我的代码中发现任何明显的优化问题吗?

    现在,我知道有两种方法可以改进它:

    >

  • 我需要为数字使用不同的类型,双精度有限,我确信有更好的非内置替代类型,它们更快(它们相乘和相加更快),更准确,我只需要有人给我指点我需要看一看,告诉我这是不是真的。

    我可以移动处理到GPU。我不知道如何做到这一点(也许是OpenGL?DirectX?有那么简单吗,还是我需要学很多东西?)。如果有人能给我发链接到适当的教程关于这个主题或告诉我一般这将是伟大的。

    非常感谢您的阅读,希望您能帮助我:)

  • 共有3个答案

    阎安邦
    2023-03-14

    要将处理移到GPU,这里有很多很好的例子:

    https://www.shadertoy.com/results?query=mandelbrot

    请注意,您需要一个支持WebGL的浏览器来查看该链接。在Chrome中效果最好。

    我不是分形方面的专家,但您似乎已经在优化方面取得了长足的进步。超出这一点可能会使代码更难阅读和维护,因此您应该问问自己这是值得的。

    我在其他分形程序中经常观察到的一种技术是:在缩放时,以较低的分辨率计算分形,并在渲染时将其拉伸到全尺寸。然后渲染在全分辨率尽快缩放停止。

    另一个建议是,当您使用多个线程时,您应该注意每个线程不读取/写入其他线程的内存,因为这将导致缓存冲突并损害性能。一个好的算法是将工作分成扫描线(而不是像你现在这样四分之一)。创建多个线程,然后只要还有行要处理,就将扫描线分配给可用的线程。让每个线程将像素数据写入本地内存,并在每行之后将其复制回主位图(以避免缓存冲突)。

    扈德容
    2023-03-14

    WRT编码的GPU,你可以看看Cudafy。Net(它也做OpenCL,它与NVidia没有联系)开始了解正在发生的事情,甚至可能做你需要的一切。我很快发现它——还有我的显卡——不适合我的需要,但对于你所处的舞台上的Mandelbrot来说,应该没问题。

    简而言之:你用一种C的味道为GPU编码(通常是Cuda C或OpenCL),然后将“内核”(你编译的C方法)推送到GPU,然后调用“内核”,通常用参数来说明什么数据使用——或者几个参数来告诉它将结果放在内存的哪里。

    当我自己进行分形渲染时,由于已经概述的原因,我避免了绘制位图,并推迟了渲染阶段。除此之外,我倾向于编写大量的多线程代码,这对于尝试访问位图来说非常糟糕。取而代之的是,我向一个普通的存储区写东西——最近我使用了一个MemoryMappedFile(一个内置的.Net类),因为这给了我相当不错的随机访问速度和一个巨大的可寻址区域。我还倾向于将结果写入队列,并让另一个线程处理将数据提交到存储器的问题;每个Mandelbrot像素的计算时间将是“参差不齐的”——也就是说,它们并不总是需要相同的时间长度。因此,像素提交可能成为极低迭代计数的瓶颈。将其分配给另一个线程意味着您的计算线程永远不会等待存储完成。

    我目前正在玩Mandelbrot集合的Buddhabrot可视化,考虑使用GPU扩展渲染(因为它需要很长时间使用CPU),并获得巨大的结果集。我本来想把目标定在一个8千兆像素的图像上,但是我意识到我需要脱离像素的限制,可能由于精度问题而远离浮点运算。我还将不得不购买一些新的硬件,这样我就可以与GPU进行不同的交互——不同的计算任务将在不同的时间完成(根据我之前的迭代计数评论),所以我不能只启动一批线程,等待它们全部完成,而不可能浪费大量时间等待一个特别高的线程迭代从整个批次中计数。

    另一个我很少看到的关于Mandelbrot集的观点是,它是对称的。你的计算量可能是你需要的两倍。

    柯宜年
    2023-03-14

    如果您决定将处理移到gpu,可以从许多选项中进行选择。由于您使用的是C#,XNA将允许您使用HLSL。如果选择此选项,RB Whitaker将提供最简单的XNA教程。另一个选择是OpenCL。OpenTK附带了julia集分形的演示程序。修改以显示mandlebrot集合将非常简单。请参见此处,请记住查找源代码附带的GLSL着色器。

    关于GPU,示例对我没有帮助,因为我完全不知道这个主题,它是如何工作的,GPU可以做什么样的计算(或者它是如何访问的?)

    不同的GPU软件工作方式不同,但是。。。

    通常,程序员将使用着色器语言(如HLSL、GLSL或OpenCL)为GPU编写程序。用C#编写的程序将加载着色器代码并对其进行编译,然后使用API中的函数将作业发送到GPU,然后返回结果。

    看看FX Composer或渲染猴子,如果你想用着色器练习,不用担心API。

    如果您使用的是HLSL,则呈现管道如下所示。

    顶点着色器负责获取三维空间中的点,并计算它们在二维视野中的位置。(因为您在2D中工作,所以对您来说不是什么大问题)

    像素着色器负责在顶点着色器完成后对像素应用着色器效果。

    OpenCL是一个不同的故事,它面向通用GPU计算(即:不仅仅是图形)。其功能更强大,可用于GPU、DSP和构建超级计算机。

     类似资料:
    • 不必要的效率考虑往往是性能问题的万恶之源。 ——William Allan Wulf     在第12章『速度的曲率』我们学习如何用Instruments来诊断Core Animation性能问题。在构建一个iOS app的时候会遇到很多潜在的性能陷阱,但是在本章我们将着眼于有关绘制的性能问题。

    • 问题内容: 我已经搜索了好几个小时,却无法找到明确明确的答案。我有一个需要在屏幕上绘制运动场(包括所有沥青线)的应用程序。到目前为止,我已经扩展了SurfaceView并几乎复制了LunarLander演示的其余部分。从插座也收到了应用程序将间距绘制到正确尺寸所需的所有数据,该插座也能正常工作。但是,在onDraw()函数的最后一刻,我正在绘制每帧的所有线条,这在模拟器中导致相当慢的帧速率(例如〜

    • 我正在编写一个Java程序来显示我的入门编程类的Mandelbrot集。我相信我已经正确地设置了所有的数学,但是当我尝试绘制分形时,我得到的只是一种纯色。我已经测试了数学,它似乎应该是有效的。我搜索了一个多小时,但没有找到任何有用的东西。下面是我的复数类,并实际创建了Mandelbrot集:复数 曼德布罗特 我已经做了一些JUnit测试,上面的两个类似乎都可以工作。我的测试中可能有一个缺陷导致了疏

    • 之前在C#上发布了一个问题。Net Mandelbrot集合,这是一个很有帮助的回答,但是我必须回到这个Mandelbrot集合,在它的双变量上实现一个结构,定义(虚坐标和实坐标)。 作为一个刚接触structs并且对structs有点生疏的人,我想就我做错了什么以及如何改进所述代码提出一些建议,因为只要看看它,我相信它可以稍微优化一下。这是使用结构的正确方法吗?如果没有,有哪些替代方案或最佳技术

    • 我试图画曼德布罗特集,其中的点是黑色的,其他的都是白色的。在这个初始版本中,我不希望能够放大,而只是创建一个静态图像。 我创建了一个ComplexNumber类,如下所示,用于处理平方运算和将复数相加。 这是我渲染GUI并实际计算Mandelbrot Set中的点的代码。 运行完这段代码后,我得到了下图。看起来曼德尔布罗特的布景有一点模糊,但随后被一吨黑色遮住了。我做错了什么? 更新的解决方案如下

    • 我有一个抛物线图,抛物线方程的系数存储在数组中。在(mousemotionlistener)中,抛物线的系数被更改,我想用新的系数实时更新抛物线图。我怎么才能让这一切发生?