优化Mandelbrot分形

时间:2017-06-04 13:27:17

标签: c++

这是一个以.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中我可以复制另一半。 我在考虑loghartimic的力量,但尝试了一些东西,只适用于整数......

3 个答案:

答案 0 :(得分:1)

所以这是热门循环:

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;

那就是人们。

答案 1 :(得分:0)

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

答案 2 :(得分:0)

有许多方法可以优化曼德罗分形。

一种方法是为您的CPU甚至GPU优化代码。 Mandelbrot with SSE, AVX and OpenCL显示了令人印象深刻的加速。这样可以将内部循环优化近1000倍。快3个数量级。

但是还有其他优化方法。您已经提到的第一个:mandelbrot集被镜像为y = 0。因此,您只需要一半。还有一些更简单的方法可以避免运行内部循环。如果您在Wikipedia on Mandelbrot上向下滚动至“优化”,则会看到“心形/灯泡检查”。这是对苹果形主体部分或其直接左侧的圆中的点的简单检查。对于可以涵盖很多要点的海报。

我看到的另一种提速方法是在生成预览或仅以黑白设置的mandelbrot轮廓时使用距离估计(也在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个迭代。随着网格的划分越来越多,加速变得越来越小。但是节省下来的每一点加起来。

此方法的优点是可以平滑/连续地着色或映射到高度。其他方法仅适用于将迭代映射到色带。