簡體   English   中英

使C#mandelbrot繪圖更有效率

[英]Making C# mandelbrot drawing more efficient

首先,我知道這個問題聽起來好像我沒有搜索,但我做了很多。

我為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;

            }
        }

我該怎么做才能改善這一點? 您在我的代碼中發現任何明顯的優化問題嗎?

現在有2種方法我知道我可以改進它:

  1. 我需要為數字使用不同的類型,double是精確限制的,我敢肯定有更好的非內置替代類型更快(它們繁殖和添加更快)並且具有更高的准確性,我只需要有人來指出我需要看的地方並告訴我它是否屬實。

  2. 我可以將處理轉移到GPU。 我不知道如何做到這一點(也許是OpenGL?DirectX?它甚至是那么簡單還是我需要學習很多東西?)。 如果有人可以向我發送有關此主題的正確教程的鏈接,或者告訴我一般情況下這將是很好的。

非常感謝您閱讀,希望您能幫助我:)

如果您決定將處理移至gpu,則可以從多個選項中進行選擇。 由於您使用的是C#,XNA將允許您使用HLSL。 如果您選擇此選項, RB Whitaker將提供最簡單的XNA教程。 另一種選擇是OpenCL OpenTK附帶了一個julia集分形的演示程序。 這將非常簡單地修改以顯示mandlebrot集。 請參閱此處請記住找到與源代碼一起使用的GLSL着色器。

關於GPU,示例對我沒有幫助,因為我完全不了解這個主題,它是如何工作的以及GPU可以做什么樣的計算(或者甚至如何訪問?)

不同的GPU軟件工作方式不同......

通常,程序員將使用着色器語言(例如HLSL,GLSL或OpenCL)為GPU編寫程序。 用C#編寫的程序將加載着色器代碼並對其進行編譯,然后使用API​​中的函數將作業發送到GPU並在之后返回結果。

如果您想要使用着色器進行一些練習而不必擔心API,請查看FX Composer或渲染猴子。

如果您使用的是HLSL,渲染管道將如下所示。

管道

頂點着色器負責在3D空間中獲取點並計算它們在2D視場中的位置。 (因為你在2D工作,對你來說不是很重要)

像素着色器負責在頂點着色器完成后將着色器效果應用於像素。

OpenCL是一個不同的故事,它面向通用GPU計算(即:不僅僅是圖形)。 它功能更強大,可用於GPU,DSP和構建超級計算機。

對GPU進行WRT編碼,您可以查看Cudafy.Net(它也是OpenCL,它與NVidia無關),以便開始了解正在發生的事情,甚至可以在那里完成所需的一切。 我很快發現它 - 和我的顯卡 - 不適合我的需要,但對於你所處的階段的Mandelbrot,應該沒問題。

簡而言之:您使用C(Cuda C或OpenCL正常)編寫GPU代碼然后將“內核”(您編譯的C方法)推送到GPU,然后是任何源數據,然后經常調用“內核”使用參數來說明要使用的數據 - 或者可能是一些參數來告訴它將結果放在其內存中的位置。

當我自己進行分形渲染時,由於已經概述的原因,我避免繪制到位圖,並推遲了渲染階段。 除此之外,我傾向於編寫大量多線程代碼,這對於嘗試訪問位圖非常不利。 相反,我寫了一個公共存儲 - 最近我使用了MemoryMappedFile(內置.Net類),因為這給了我相當不錯的隨機訪問速度和巨大的可尋址區域。 我也傾向於將我的結果寫入隊列並讓另一個線程處理將數據提交到存儲器; 每個Mandelbrot像素的計算時間將“粗糙” - 也就是說它們不會總是占用相同的時間長度。 因此,您的像素提交可能是非常低迭代次數的瓶頸。 將其轉移到另一個線程意味着您的計算線程永遠不會等待存儲完成。

我現在正在使用Mandelbrot集的Buddhabrot可視化,看着使用GPU來縮小渲染(因為它需要花費很長時間來使用CPU)並且具有巨大的結果集。 我正在考慮定位一個8千兆像素的圖像,但我已經意識到我需要偏離像素的約束,並且可能由於精度問題而遠離浮點運算。 我也將不得不購買一些新的硬件,這樣我就可以與GPU進行不同的交互 - 不同的計算工作將在不同的時間完成(根據我先前的迭代計數評論),所以我不能只是批量線程並等待為了他們所有人完成而不會浪費大量時間等待整批中的一個特別高的迭代計數。

讓我幾乎沒有看到關於Mandelbrot Set的另一個觀點是它是對稱的。 你可能需要做兩倍的計算。

要將處理轉移到GPU,您可以在此處獲得許多優秀示例:

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

請注意,您需要支持WebGL的瀏覽器才能查看該鏈接。 在Chrome中效果最佳。

我不是分形專家,但你似乎已經在優化方面走得很遠了。 超出這個范圍可能會使代碼更難以閱讀和維護,所以你應該問自己這是值得的。

我在其他分形程序中經常觀察到的一種技術是:在縮放時,以較低分辨率計算分形並在渲染過程中將其拉伸至完整大小。 變焦停止后立即以全分辨率渲染。

另一個建議是,當你使用多個線程時,你應該注意每個線程不讀/寫其他線程的內存,因為這會導致緩存沖突並損害性能。 一個好的算法可以在掃描線中分割工作(而不是像現在這樣四分之四)。 創建多個線程,然后只要剩下要處理的行,就將掃描線分配給可用的線程。 讓每個線程將像素數據寫入本地內存塊,並在每行之后將其復制回主位圖(以避免緩存沖突)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM