1

I have the following Mandelbrot set code in OpenMP. My C code works just fine, and the picture that it produces is perfect. But using OpenMP, it compiles and runs correctly, but unfortunately I am not able to open the output .ppm file, simply Gimp cannot read it.

// mandopenmp.c
// to compile: gcc -fopenmp mandopenmp.c -o mandopenmp -lm
// usage: ./mandopenmp <no_of_iterations> > output.ppm

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <omp.h>

typedef struct {
    int r, g, b;
} rgb;


void color(rgb **m, int x, int y, int red, int green, int blue)
{
    m[x][y].r = red;
    m[x][y].g = green;
    m[x][y].b = blue;
}

void mandelbrot(int niterations, rgb **m)
{
    int w = 600, h = 400, x, y, i;
    // each iteration, it calculates: newz = oldz*oldz + p, 
    // where p is the current pixel, and oldz stars at the origin
    double pr, pi;                   // real and imaginary part of the pixel p
    double newRe, newIm, oldRe, oldIm;   // real and imaginary parts of new and old z
    double zoom = 1, moveX = -0.5, moveY = 0; // you can change these to zoom and change position

    printf("P6\n# AUTHOR: Erkan Tairi\n");
    printf("%d %d\n255\n",w,h);

    //loop through every pixel
    #pragma omp parallel for private(x,i,pr,pi,newRe,newIm,oldRe,oldIm) schedule(dynamic, 1)
    for(y = 0; y < h; y++) {
        for(x = 0; x < w; x++) {
            // calculate the initial real and imaginary part of z, 
            // based on the pixel location and zoom and position values
            pr = 1.5 * (x - w / 2) / (0.5 * zoom * w) + moveX;
                pi = (y - h / 2) / (0.5 * zoom * h) + moveY;
                newRe = newIm = oldRe = oldIm = 0; //these should start at 0,0
                // start the iteration process
                for(i = 0; i < niterations; i++) {
                        // remember value of previous iteration
                        oldRe = newRe;
                        oldIm = newIm;
                        // the actual iteration, the real and imaginary part are calculated
                        newRe = oldRe * oldRe - oldIm * oldIm + pr;
                        newIm = 2 * oldRe * oldIm + pi;
                        // if the point is outside the circle with radius 2: stop
                        if((newRe * newRe + newIm * newIm) > 4) break;
                }
                if(i == niterations)
                color(m, x, y, 0, 0, 0); // black
            else
            {
                // normalized iteration count method for proper coloring
                double z = sqrt(newRe * newRe + newIm * newIm);
                int brightness = 256. * log2(1.75 + i - log2(log2(z))) / log2((double)niterations);
                color(m, x, y, brightness, brightness, 255);
            }
            }
    }
}

int main(int argc, char *argv[])
{
    int niterations, i, j;

    if(argc != 2)
    {
        printf("Usage: %s <no_of_iterations> > output.ppm\n", argv[0]);
        exit(1);
    }

    niterations = atoi(argv[1]);

    rgb **m;
    m = malloc(600 * sizeof(rgb *));
    for(i = 0; i < 600; i++)
        m[i] = malloc(400 * sizeof(rgb));

    double begin = omp_get_wtime();
    mandelbrot(niterations, m);

    for(i = 0; i < 600; i++) {
        for(j = 0; j < 400; j++) {
            fputc((char)m[i][j].r, stdout);
            fputc((char)m[i][j].g, stdout);
            fputc((char)m[i][j].b, stdout);
        }
    }

    double end = omp_get_wtime();

    double time_spent = end - begin;
    fprintf(stderr, "Elapsed time: %.2lf seconds.\n", time_spent);

    for(i = 0; i < 600; i++)
        free(m[i]);
    free(m);

    return 0;
}
  • You may also be interested in the code here https://stackoverflow.com/questions/48069990/multithreaded-simd-vectorized-mandelbrot-in-r-using-rcpp-openmp – Tom Wenseleers Jan 24 '18 at 16:18

3 Answers3

4

I don't know the internals of the Mandrelbot set, but I will give a shot based on your program workflow.

Probably it is because you are writing the color to your output file while in the parallel section. What this this means is that your pixels are being written as the computing process finishes, but this does not mean that the computing process of pixel X will end before the processing of pixel X+1.

This way, while writing to the file, you will end up writing first (for example) pixel X+1 and then pixel X, mixing the colors.

Try writing the output result to a matrix. You will have to change your color function, adding two parameters i and j with the coordinates of the pixel to be written.

After the whole processing finishes and every pixel is computed, then will should write the pixels of the matrix to the output file.

The code:

typedef struct {
    int r, g, b;
} rgb;

void color(rgb **m, int x, int y, int red, int green, int blue) {
    m[x][y].r = red;
    m[x][y].g = green;
    m[x][y].b = blue;
}

void mandelbrot(rgb **m, int niterations) { // note the new argument, m.
    // and your code goes on and on... until:
            if ( i == niterations )
                color(m, x, y, 0, 0, 0);
            else {
                // normalized iteration count method for proper coloring
                double z = sqrt(newRe * newRe + newIm * newIm);
                int brightness = 256. * log2(1.75 + i - log2(log2(z))) / log2((double)niterations);
                color(m, x, y, brightness, brightness, 255);
            }
        }
    }
}

int main(int argc, char *argv[]) {
    // everything ok until...

    double begin = omp_get_wtime();

    rgb **m;
    m = malloc(sizeof(rgb*) * 600);
    for ( i = 0; i < 600; i++ ) {
        m[i] = malloc(400 * sizeof(rgb));

    // finally call mandelbrot!
    mandelbrot(m, niterations);
    double end = omp_get_wtime();

    // now that you have computed your set, you just walk the array writing the output to the file.

    for ( i = 0; i < 600; i++ ) {
        free(m[i]);
    }
    free(m);

    double time_spent = end - begin;
    fprintf(stderr, "Elapsed time: %.2lf seconds.\n", time_spent);

    return 0;
}
  • Yes, I thought of this, while in parallel any thread can finish its execution before the other thread. But I really don't know how to fix it. Is it possible for you, to write a short code example about what you mean? –  Apr 21 '13 at 22:31
  • Posted some code. Not very well written, but the idea is given (I'm sorry, I'm in a hurry... but wanted to help anyway :) ). Please, comment here if you still have any questions. – Vinícius Gobbo A. de Oliveira Apr 21 '13 at 22:44
  • What will be my matrix size, it will be my picture size, meaning 600x400? I edited my original code, and made some stupid changes. Though now I get lots of errors. I guess they are mainly regarding allocating and deallocating my matrix. –  Apr 21 '13 at 23:06
  • @erkant Exactly! Your allocation loop is wrong. Take a look at the updated code. What errors are you receiving? Compilation or runtime errors? – Vinícius Gobbo A. de Oliveira Apr 21 '13 at 23:31
  • Okay, I changed my code again, now it compiles correctly, but unfortunately this time, when I try to open my output .ppm file with gimp, I get the following error: Opening 'file.ppm' failed: PNM Image plug-in could not open image. I have changed my original post according to my current code. –  Apr 22 '13 at 17:44
  • This is some issue with the program not generating the file correctly. I don't know how the PNM format works, so I'm not the most recommended person to help with this. I'll have some spare time later, then I'll take a look at how this format works and them I'll implement some fixes. – Vinícius Gobbo A. de Oliveira Apr 22 '13 at 18:00
  • Also, please post your code with the matrix being outputed to the file, so I'm able to debug it. – Vinícius Gobbo A. de Oliveira Apr 22 '13 at 18:04
  • I also tried using my first version, which didn't include the rgb structure and the m matrix. And I managed to make my program work and output the file correctly using just the #pragma omp ordered directive, which is explained in the other reply. Though this time I didn't achieve any speed-up. –  Apr 22 '13 at 18:25
  • I just noticed my huge mistakes, when I finished the calculations, I have forgotten to walk through the matrix and write the values to a file. LOL, how stupid. Because I didn't see the code for this part in your answer, and I totally forgot it. Can you also include it for me? –  Apr 22 '13 at 18:33
  • Okay, I wrote that part also, I go through the matrix and write them to the file, but it gets printed three times, and mixed. I guess my loops for writing them to a file are wrong. –  Apr 22 '13 at 18:44
  • Using the above code, I get it printed three times, and 90 degree rotated, I guess I need to change the values for x and y, and do something else regarding writing the values to a file. –  Apr 22 '13 at 19:10
  • @erkant Interesting... here it printed it correctly, 600x400 pixels..., nor 400x600... – Vinícius Gobbo A. de Oliveira Apr 22 '13 at 19:33
  • @erkant Now I see! It really is an issue with the X and Y variables getting mixed. Easy and dirty fix: make your output code correct this problem... – Vinícius Gobbo A. de Oliveira Apr 22 '13 at 19:35
  • Sorry, I didn't get your last comment? –  Apr 22 '13 at 19:44
  • @erkant Please, desconsider my last comment. I really don't know what I was thinking when I posted it. – Vinícius Gobbo A. de Oliveira Apr 22 '13 at 19:52
  • Oh, okay. Can you help me overcome this problem, and make it work properly? If you are busy now, I can wait till tomorrow. –  Apr 22 '13 at 19:58
  • @erkant Guess I found it! I haven't tested, but reading your code, I think the problem is at the `color` function. It is messing with the coordinates. Switch the `x` and `y` as the arrays indexes and it may work (change `m[x][y]` to `m[y][x]`). – Vinícius Gobbo A. de Oliveira Apr 22 '13 at 21:07
  • Yes, it worked. I needed to change y and x as you said, and then everywhere in my main function, replace 400 with 600 and vice versa. Now it works perfectly. Thank you very much! –  Apr 22 '13 at 22:44
  • @erkant Nice! Post again if u need! – Vinícius Gobbo A. de Oliveira Apr 23 '13 at 02:20
  • Thanks for everything. Is there any way that I can achieve more speed-up with my OpenMP code? –  Apr 23 '13 at 19:37
  • @erkant There are few things, but not much. I don't think you can achieve speeds too much higher than you already got. But I'm talking only about the code point of view, not about the algorithm of the mandrelbot. I'll dig deeper into this tomorrow... it is past midnight in my country... – Vinícius Gobbo A. de Oliveira Apr 25 '13 at 03:23
  • @erkant Replace `w`, `h`, `moveX`, `moveY`, `zoom` with constants or defines. You can also replace constant expressions like `w / 2` with constants or defines (the compiler will probably optimize, but let's help him!). Exercise for to find these expressions (there aren't many). From the code point of view, I believe that's all. Post here the performance improvements (note that the compiler may have already done it for you, depending on the optimization flags you set...). – Vinícius Gobbo A. de Oliveira Apr 26 '13 at 00:32
1

Your implementation is flawed. You have declared many variables that have to be private to be shared instead. This includes pr, pi, newRe, newIm. Also oldRe and oldIm are shared by default as they are declared in a scope that is outer to the parallel region. These all should be private instead:

#pragma omp parallel for private(x,i,pr,pi,newRe,newIm,oldRe,oldIm)

Also the default scheduling for parallel for loops is often (but not necessarily always) static. This is not the optimal one for things like fractals as it takes different time to compute each line or column in the image. Therefore you should apply the schedule(dynamic,1) clause and play with the chunk size (1 in this case) until you get the best speed-up.

#pragma omp parallel for private(x,i,pr,pi,newRe,newIm,oldRe,oldIm) \
            schedule(dynamic,1)
Hristo Iliev
  • 72,659
  • 12
  • 135
  • 186
  • Thanks for this piece of information, it helped me achieve some speed-ups. –  Apr 22 '13 at 19:47
0

If you're writing to a file sequentially (which you were doing in your original code before you edited it) then you can use the ordered pragma before you write to the file. This will make the image correct using your original code. See the following link http://bisqwit.iki.fi/story/howto/openmp/#ExampleCalculatingTheMandelbrotFractalInParallel

However, it's not the optimal solution. The optimal solutions is to write to a memory buffer first and after the mandelbrot code finishes filling the buffer then write to a file (as you do in your new code).

I have a few suggestions to speed your code up. Fuse your x and y loop (as shown in the link) and use schedule dynamic (also shown in that link) since each pixel takes different time. Lastly, use SSE/AVX to operate on two (SSE) or four (AVX) pixels at once. In total you should get a speed up of over 20x with OpenMP and SSE/AVX.

#pragma omp ordered {
    if(i == niterations)
        color(m, x, y, 0, 0, 0); // black - use original color function which writes to file
    else
    {
        // normalized iteration count method for proper coloring
        double z = sqrt(newRe * newRe + newIm * newIm);
        int brightness = 256. * log2(1.75 + i - log2(log2(z))) / log2((double)niterations);
        color(m, x, y, brightness, brightness, 255); //use original color function which writes to file
    }
}
  • I saw the link, thanks for it. Can you help me achieve more speed-up? –  Apr 23 '13 at 16:30
  • I would check out the following link http://www.bealto.com/mp-mandelbrot.html . It does not use AVX however so you can speed it up quite a bit on the CPU. I have code that does that but I would get your scaler code working first. I plan to put up a site with my results at some point... –  Apr 23 '13 at 19:39