多线程在速度上没有任何改进 - 在C中使用pthread - 为什么?

时间:2017-05-16 17:04:47

标签: c multithreading pthreads performance-testing

为了在多线程中变得更加舒适,我编写了一个带有“密集”计算的小程序。它是mandelbrot集的图片,其中每个像素单独计算,然后像素缓冲到行。每个线程获得的总行数相等。因此,例如,如果选择的线程数是2,则具有1000行高度计算的图片应该在两个500行包中结束。因此我建议速度种类减少两倍,但没有改善。为什么???我不明白,因为一切正常并且看似合乎逻辑。如果有人能给我一个提示,我将非常感激。 下面你会看到main和一个由main调用的mandelbrot集的caluclation函数。

int main(int argc, char ** argv, char ** envp) {

if(argc != 4)
{
printf("Bitte genau 3 Argumente eingeben.\n");
 return 1;
}
//Structs und Variablen für die Stopuhr
struct timeval start, ende;
long ttlende, ttlstart;

width  = str2num(argv[1]);
height = str2num(argv[2]);

int y;
//char blueGreenRed[3];
//Ist Buffer für ganze Zeile: Breite * 3 wegen den 3 Bytes pro Pixel
//char zeile[width*3];

unsigned char info[BMPHEADER_SIZE] = {
              //size
    'B','M',  0,0,0,0, 0,0, 0,0, 54,0,0,0,
              //width  //height
    40,0,0,0, 0,0,0,0, 0,0,0,0,  1,0, 24,0,
              // datasize
    0,0,0,0,  0,0,0,0
};

// BMP lines must be of lengths divisible by 4
char span[4] = "\0\0\0\0";
int spanBytes = 4 - ((width * 3) % 4);
if (spanBytes == 4) spanBytes = 0;
int psize = ((width * 3) + spanBytes) * height;

*( (int*) &info[2])  = BMPHEADER_SIZE + psize;
*( (int*) &info[18]) = width;
*( (int*) &info[22]) = height;
*( (int*) &info[34]) = psize;

write(1, (char *) info, BMPHEADER_SIZE);
//Stoppuhr starten, d.h. get time stamp

//create chunks
int threads= str2num(argv[3]);
int i;
int reminder = height%threads;
int blocksize = height/threads;
int rounds = height/blocksize;
int begin = 1;


//init structs
threadinfo *tinfoptr = getptr(rounds);
//threadinfo tinfo = *tinfoptr;
for (i=1; i<=rounds; ++i){
        int res = blocksize*i;
        if((i==rounds)){
                res = res+reminder;
        }

        //update parameters of tinfo
        (*(tinfoptr+(i-1))).from = begin;
        (*(tinfoptr+(i-1))).to = res;
        (*(tinfoptr+(i-1))).span = span;
        (*(tinfoptr+(i-1))).spanBytes = spanBytes;
        (*(tinfoptr+(i-1))).width = width;
        (*(tinfoptr+(i-1))).height = res-begin+1;
        (*(tinfoptr+(i-1))).results = NULL;
        (*(tinfoptr+(i-1))).threadno = i;
        (*(tinfoptr+(i-1))).blocksizeperthread = -1;
        //altes ende ist neuer start des nächsten blocks.
        begin = res;
}

fprintf(stderr,"inti abgeschlossen, starte threads\n");

pthread_t myThread[rounds];
for (i=1; i<=rounds; ++i){
    fprintf(stderr,"Rufe Thread %d auf\n",i);
    if (pthread_create(&myThread[i-1], NULL, myDo2, (void*)(tinfoptr+.   (i-1))) ) {
        fprintf(stderr, "Error creating thread\n");
        return 1;
    }
}

gettimeofday(&start, NULL);
for (i=1; i<=rounds; ++i){
    /* wait for the second thread to finish */
    if (pthread_join(myThread[i-1], NULL)) {
        fprintf(stderr, "Error joining thread\n");
        return 2;
    }
}
//Stoppuhr beenden, d.h. get time stamp, NULL per Doku.
gettimeofday(&ende,NULL);

    //if the main thread arrives this position, restulptr containts all rows indexed by the threadnr.
    for (i=1; i<=rounds; i++){
        //noch countereinbauen
        int l_blocksize = (tinfoptr+(i-1))->blocksizeperthread;
        for (y=0; y <= l_blocksize; y++) {
            //Zeilenweise nach stdout schreiben
            write(1, (tinfoptr+(i-1))->results[y], width*3);
            // BMP lines must be of lengths divisible by 4
            write(1, span, spanBytes);
        }
    }


ttlende = ende.tv_sec * 1000000 + ende.tv_usec;
ttlstart = start.tv_sec * 1000000 + start.tv_usec;
fprintf(stderr, "\nDauer: %ld Mikrosekunden\n", (ttlende - ttlstart));

return 0;
}

这里的函数叫做:

void* myDo2(void* tiptr){
threadinfo* mythread = (threadinfo*)tiptr;
//copy infos from struct to this thread
int l_from = mythread->from;
int l_to = mythread->to;
int l_width = mythread->width;
int l_height = mythread->height;
//  char **container = createMatrix(l_width*3,l_height);
char **container = malloc (l_height * sizeof(char*));
for(int i = 0; i<l_height; i++){
    container[i] = malloc(l_width*3*sizeof(char));
}

int x,y;
char iterate=0;
Complex c    = {0,0};
Complex newz = {0,0};
float imageRelation = (float)l_width/(float)height;
char blueGreenRed[3];
    //Ist Buffer für ganze Zeile: Breite * 3 wegen den 3 Bytes pro Pixel
    char zeile[l_width*3];
    int counter = 0;

for (y=l_from; y <= l_to; ++y)
{
    for (x=1; x <= l_width; ++x) {
        Complex z = {0,0};
        float quad=0;

        c.re = zoom * (-1.0 + imageRelation * ( (x-1.0) / (width-1.0)) );
        c.im = zoom * ( 0.5 - (y-1.0) / (height-1.0) );

        // iterate
        for ( iterate=1; iterate < colorLimit && quad < quadLimit; ++iterate ) {
            quad = z.re * z.re + z.im * z.im;

            newz.re = (z.re * z.re) - (z.im * z.im) + c.re;
            newz.im =  z.re * z.im * 2.0            + c.im;

            z = newz;
        }
        toRGB(iterate, blueGreenRed);
        //Kopiere 3 Bytes von bgr nach zeile + (x-1)*3
        //Beachte: Die Variable zeile ist ein character array daher wird (x-1)*3 benutzt um 3 Byte Pakete pro Pixel in die Zeile zu laden.
        memcpy((zeile + (x-1)*3), blueGreenRed, 3);
    }
    memcpy(container[counter], zeile, l_width*3);
    counter++;
}

mythread->blocksizeperthread = counter-1;
mythread->results = container;
        fprintf(stderr, "Ich bin Thread-Nr. %d\n", mythread->threadno);
        fprintf(stderr, "und habe eine Menge Zeilen von %d\n", mythread->blocksizeperthread);
        fprintf(stderr, "und habe berechnet von %d\n", l_from);
        fprintf(stderr, "und habe berechnet bis %d\n", l_to);
return NULL;
}

非常感谢, 你的 jbug

1 个答案:

答案 0 :(得分:5)

简而言之,该模型可以运行,但您需要为每个线程提供足够的工作,以便能够吸收启动,停止和同步线程的开销。并且您必须在能够运行多个同时线程的计算机上运行(多核机器)。

我拿了你提供的应用程序并将其修改为实际编译。如果我在具有许多CPU核心的Linux机器上运行给myDo2工作线程做足够的工作,那么我看到类似于以下的结果:

./test width height num_threads
./test 10000 10000 1
Dauer: 17,660,185 Mikrosekunden

./test 10000 10000 2 
Dauer: 7,864,508 Mikrosekunden

./test 10000 10000 8
Dauer: 1,100,126 Mikrosekunden

这意味着有8个线程,整个挂钟时间从17.6秒减少到1.1秒,这是一个大于8倍的改进(可能是由于更好的内存和缓存使用)。

然而,如果我给每个线程太少的工作,那么我的时代似乎没有改善,实际上在某些时候会变得更糟。

./test 10 10 1
Dauer: 70 Mikrosekunden

./test 10 10 2
Dauer: 60 Mikrosekunden

./test 10 10 4 
Dauer: 205 Mikrosekunden

在这里,您可以看到启动线程,然后停止并与该线程同步的开销大于线程内部的工作量。

所以编程模型有效,但你需要正确使用它。

我使用

在RedHat上编译了下面的代码

gcc -std = gnu99 test.c -o test -l pthread

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <pthread.h>
#include <string.h>

typedef struct _threadinfo
{
    int from;
    int to;
    int width;
    int height;
    int blocksizeperthread;
    char **results;
    int threadno;
} threadinfo;

typedef struct _cplx
{
    float re;
    float im;
} Complex;

void* myDo2( void *tiptr )
{
    threadinfo *mythread = (threadinfo *)tiptr;
    //copy infos from struct to this thread
    int l_from = mythread->from;
    int l_to = mythread->to;
    int l_width = mythread->width;
    int l_height = mythread->height;
    char **container = malloc(l_height * sizeof(char *));
    for (int i = 0; i < l_height; i++)
    {
        container[i] = malloc(l_width * 3 * sizeof(char));
    }

    int x, y;
    char iterate = 0;
    Complex c    = { 0, 0 };
    Complex newz = { 0, 0 };
    float imageRelation = (float)l_width / (float)l_height;
    char blueGreenRed[3];
    //Ist Buffer für ganze Zeile: Breite * 3 wegen den 3 Bytes pro Pixel
    char zeile[l_width * 3];                            //1000*3
    int counter = 0;
    float zoom = 1.0;
    float colorLimit = 10.0;
    float quadLimit = 10.0;

    for (y = l_from; y <= l_to; ++y)                    //1..500
    {
        for (x = 1; x <= l_width; ++x)                  //1..1000
        {
            Complex z = { 0, 0 };
            float quad = 0;

            c.re = zoom * (-1.0 + imageRelation * ((x - 1.0) / (l_width - 1.0)));
            c.im = zoom * (0.5 - (y - 1.0) / (l_height - 1.0));

            // iterate
            for (iterate = 1; iterate < colorLimit && quad < quadLimit; ++iterate)
            {
                quad = z.re * z.re + z.im * z.im;

                newz.re = (z.re * z.re) - (z.im * z.im) + c.re;
                newz.im =  z.re * z.im * 2.0            + c.im;

                z = newz;
            }
            //toRGB(iterate, blueGreenRed);
            //Kopiere 3 Bytes von bgr nach zeile + (x-1)*3
            //Beachte: Die Variable zeile ist ein character array daher wird
            //(x-1)*3 benutzt um 3 Byte Pakete pro Pixel in die Zeile zu laden.
            memcpy((zeile + (x - 1) * 3), blueGreenRed, 3);
        }
        memcpy(container[counter], zeile, l_width * 3);
        counter++;
    }

    mythread->blocksizeperthread = counter - 1;
    mythread->results = container;
    fprintf(stderr, "Ich bin Thread-Nr. %d\n", mythread->threadno);
    fprintf(stderr, "und habe eine Menge Zeilen von %d\n", mythread->blocksizeperthread);
    fprintf(stderr, "und habe berechnet von %d\n", l_from);
    fprintf(stderr, "und habe berechnet bis %d\n", l_to);
    return NULL;
}

int main(int argc, char **argv, char **envp)
{
    if (argc != 4)
    {
        printf("Bitte genau 3 Argumente eingeben.\n");
        return 1;
    }
//Structs und Variablen für die Stopuhr
    struct timeval start, ende;
    long ttlende, ttlstart;
    int width;
    int height;

    width  = atoi(argv[1]);
    height = atoi(argv[2]);

    int y;

// BMP lines must be of lengths divisible by 4
    char span[4] = "\0\0\0\0";
    int spanBytes = 4 - ((width * 3) % 4);
    if (spanBytes == 4) spanBytes = 0;
    int psize = ((width * 3) + spanBytes) * height;

//Stoppuhr starten, d.h. get time stamp

//create chunks
    int threads = atoi(argv[3]);
    int i;
    int reminder = height % threads;
    int blocksize = height / threads;
    int rounds = height / blocksize;
    int begin = 1;


//init structs
    threadinfo *tinfoptr = malloc( sizeof(threadinfo) * rounds );
//threadinfo tinfo = *tinfoptr;
    for (i = 1; i <= rounds; ++i)
    {
        //res = 500 * 1;
        //res = 500*2;
        int res = blocksize * i;
        if ((i == rounds))
        {
            res = res + reminder;
        }

        //update parameters of tinfo
        (*(tinfoptr + (i - 1))).from = begin;
        (*(tinfoptr + (i - 1))).to = res;
        (*(tinfoptr + (i - 1))).width = width;
        (*(tinfoptr + (i - 1))).height = res - begin + 1;
        (*(tinfoptr + (i - 1))).results = NULL;
        (*(tinfoptr + (i - 1))).threadno = i;
        (*(tinfoptr + (i - 1))).blocksizeperthread = -1;
        //altes ende ist neuer start des nächsten blocks.
        begin = res;
    }

    fprintf(stderr, "inti abgeschlossen, starte threads\n");

    pthread_t myThread[rounds];
    for (i = 1; i <= rounds; ++i)
    {
        fprintf(stderr, "Rufe Thread %d auf\n", i);
        if (pthread_create(&myThread[i - 1], NULL, myDo2,
                           (void *)(tinfoptr + (i - 1))))
        {
            fprintf(stderr, "Error creating thread\n");
            return 1;
        }
    }

    gettimeofday(&start, NULL);
    for (i = 1; i <= rounds; ++i)
    {
        /* wait for the second thread to finish */
        if (pthread_join(myThread[i - 1], NULL))
        {
            fprintf(stderr, "Error joining thread\n");
            return 2;
        }
    }
//Stoppuhr beenden, d.h. get time stamp, NULL per Doku.
    gettimeofday(&ende, NULL);

    ttlende = ende.tv_sec * 1000000 + ende.tv_usec;
    ttlstart = start.tv_sec * 1000000 + start.tv_usec;
    fprintf(stderr, "\nDauer: %ld Mikrosekunden\n", (ttlende - ttlstart));

    return 0;
}