簡體   English   中英

優化 Mandelbrot 分形

[英]Optimizing Mandelbrot fractal

這是一個在 .ppm 文件中輸出 mandelbrot 分形的代碼。 我怎樣才能優化這個?

#include<bits/stdc++.h>
using namespace std;

int findMandelbrot(double cr, double ci, int max_iterations)
{
    int i = 0;
    double zr = 0.0, zi = 0.0;
    while (i < max_iterations && zr * zr + zi * zi < 4.0)
    {
        double temp = zr * zr - zi * zi + cr;
        zi = 2.0 * zr * zi + ci;
        zr = temp;
        ++i;
    }
    return i;
}

double mapToReal(int x, int imageWidth, double minR, double maxR)
{
    double range = maxR - minR;
    return x * (range / imageWidth) + minR;
}

double mapToImaginary(int y, int imageHeight, double minI, double maxI)
{
    double range = maxI - minI;
    return y * (range / imageHeight) + minI;
}

int main()
{
    ifstream f("input.txt");
    int imageWidth, imageHeight, maxN;
    double minR, maxR, minI, maxI;

    if (!f)
    {
        cout << "Could not open file!" << endl;
        return 1;
    }

    f >> imageWidth >> imageHeight >> maxN;
    f >> minR >> maxR >> minI >> maxI;

    ofstream g("output_image.ppm");
    g << "P3" << endl;
    g << imageWidth << " " << imageHeight << endl;
    g << "255" << endl;


    double start = clock();

    for (int i = 0; i < imageHeight; i++)
    {
        for (int j = 0; j < imageWidth; j++)
        {
            double cr = mapToReal(j, imageWidth, minR, maxR);
            double ci = mapToImaginary(i, imageHeight, minI, maxI);

            int n = findMandelbrot(cr, ci, maxN);

            int r = ((int)sqrt(n) % 256);
            int gr = (2*n % 256);
            int b = (n % 256);

            g << r << " " << gr << " " << b << " ";
        }
        g << endl;

        if(i == imageHeight / 2) break;
    }

    cout << "Finished!" << endl;

    double stop = clock();

    cout << (stop-start)/CLOCKS_PER_SEC;
    return 0;
}

我一直到 imageHeight / 2 ,因為在 photoshop 中我可以復制另一半。 我在考慮對數冪但嘗試了一些東西並且只適用於整數......

有很多方法可以優化曼德布羅分形。

一種方法是優化 CPU 甚至 GPU 的代碼。 令人印象深刻的加速表現在: Mandelbrot with SSE, AVX and OpenCL 這將內部循環優化了近 1000 倍。這快了 3 個數量級。

但是還有其他優化方法。 您已經提到的第一個:mandelbrot 集在 y=0 處鏡像。 所以你只需要一半。 有一些更簡單的方法可以避免運行內循環。 如果您在 Mandelbrot 的 Wikipedia 上向下滾動到優化,您會看到“心形/燈泡檢查”。 這是一個簡單的檢查蘋果形狀的主要部分或它直接左側的圓圈中的點。 對於一張可以涵蓋很多點的海報。

我見過的另一個加速是在生成預覽時使用距離估計(也在 Wikipedia 上)方法,或者只是在黑白中設置 mandelbrot 的輪廓。 計算圖像中的隨機點,如果在 mandelbrot 集之外,則繪制一個圓,半徑由距離估計方法給出。 那個圈子里的任何東西都不是曼德布羅集的一部分。 這可以快速覆蓋 mandelbrot 集之外的許多點。

還有一些近似結果的方法可能不會產生完美的圖像,但通常已經足夠好了。 例如:

  • 邊界追蹤

計算像素經過 N 和 N+1 次迭代以逃逸的邊界之后的點。 N+1 和 N+2 相同。 這兩個邊界之間的一切都需要 N 次迭代。

  • 分而治之的盒子

計算矩形的邊界(從整個圖像開始)。 如果邊框都進行 N 次迭代,則矩形的內部進行 N 次迭代並被填充。否則將矩形分成 4 份並重復每個。 通過計算 2 或 3 像素寬的邊框可能會改善結果,但節省的會更少。

  • 猜測

以低分辨率計算圖像,然后將分辨率加倍,保留計算出的點。 遍歷圖像,如果原始點的 5x5 具有相同的 N,則圍繞內部 3x3 點填充一個矩形(也適用於 3x3、7x7、9x9...點)。 未填寫的點數計算。 然后重復整個過程,直到你得到最終的解決方案。

  • 單軌道迭代

這是最難做到的,我只見過它的一種實現。 這個想法是靠近在一起的點在迭代中表現相同。 如果您迭代一個 3x3 點的網格(覆蓋整個圖像開始),您可以使用牛頓插值對中間點的值進行插值。 這很有效 - 直到它沒有。

因此,除了 3x3 點網格之外,您還迭代 4 個測試點,每個網格單元的中間。 對這 13 個點進行 8 次迭代,然后從網格點插入 4 個測試點。 如果計算結果和插值結果相差太多(這是困難的部分),您可以丟棄最后 8 次迭代並將網格細分為 4 個半大小的網格。 您插入的缺失點。 重復直到達到最終分辨率。

即使它只適用於幾次迭代,潛在的好處也是非常大的。 假設您想要 40000 x 40000 像素的圖像。 如果 SOI 在第一次細分之前工作 10 次循環(80 次迭代),那么您可以通過計算 1040 和一些插值和檢查來節省 80 * 40000 * 40000 = 128_000_000_000 次迭代。 或者加速 123_076_923,8 個數量級。 但僅適用於前 80 次迭代。 隨着網格划分得越來越多,加速變得越來越小。 但是節省的每一點都會加起來。

這種方法的優點在於它適用於平滑/連續着色或映射到高度。 其他方法僅適用於將迭代映射到色帶。

所以這是熱循環:

int i = 0;
double zr = 0.0, zi = 0.0;
while (i < max_iterations && zr * zr + zi * zi < 4.0)
{
    double temp = zr * zr - zi * zi + cr;
    zi = 2.0 * zr * zi + ci;
    zr = temp;
    ++i;
}
return i;

我知道如何在快速 CPU 指令中實現非整數冪,但它不會讓您擺脫束縛,因為它根本不適用於復數。 使用 std::complex 也根本沒有幫助。 您不會為非內聯支付任何費用,當然也無法在發現優化時應用它們。 所以我能做的最好的是:

int i = max_iterations;
double zr = 0.0, zi = 0.0;
do {
    double temp = zr * zr - zi * zi + cr;
    zi = 2.0 * zr * zi + ci;
    zr = temp;
} while (--i && zr * zr + zi * zi < 4.0)
return max_iterations - i;

是的,我知道在循環之外進行一次整數測試並沒有買太多。 我只找到了另一個優化器,您必須檢查它是否真的更快:

int i = max_iterations;
double zr = 0.0, zi = 0.0;
do {
    double tempr = zr * zr - zi * zi + cr;
    double tempi = zr * zi;
    zi = tempi + tempi + ci;
    zr = tempr;
} while (--i && zr * zr + zi * zi < 4.0);
return max_iterations - i;

這就是人們的全部。

findMandelbrot您在循環測試中使用表達式zr * zrzi * zi但如果測試成功,則重新計算相同的兩個表達式。 因此,一個明顯的改進可能是緩存那些具有類似...

int findMandelbrot (double cr, double ci, int max_iterations)
{
  int i = 0;
  double zr = 0.0, zi = 0.0;
  double zr2 = 0.0, zi2 = 0.0;
  while (i < max_iterations && zr2 + zi2 < 4.0) {
    double temp = zr2 - zi2 + cr;
    zi = 2.0 * zr * zi + ci;
    zr = temp;
    zr2 = zr * zr;
    zi2 = zi * zi;
    ++i;
  }
  return(i - 1);
}

暫無
暫無

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

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