为什么我的线程合并排序比基本的递归合并排序慢?

时间:2019-11-29 12:29:42

标签: c multithreading algorithm mergesort

我是一名学生,需要使用线程实现MergeSort。当我测试程序时,与递归排序相比,使用线程进行排序需要花费相同或更多的时间。

我们获得了带有基本MergeSort的预制文件,并且不允许更改merge()或mergesort()功能,但是我需要实现submergeSortThreads()。 我正在用信号量计数线程(用户决定在排序过程中将创建多少个线程),没有信号量数组就无法正确排序,因为我认为是竞争条件...

我正在运行程序,并使用以下命令从文件中读取数字:

$ time cat rand_stevila.bin | ./mergeSort 10000

线程运行(n ==允许的最大线程数):

$ time cat rand_stevila.bin | ./mergeSort 10000 -t -n 5

这是代码的某些部分,对于该问题而言很重要:

    sem_t *sem_c;
    int *polje;
    unsigned int max_paralelizacij = 1000;

    struct{
        int *array;
        int min;
        int max;
        void(*function)(int *, int, int, int, int);
    }typedef thread_args;

    void* thread_fun(void* args){
        thread_args *thr_arg = (thread_args*)args;
        mergeSort((*thr_arg).array,(*thr_arg).min,(*thr_arg).max,(*thr_arg).function);
        return 0;
    }


    // simple recursive sort
    void submergeSortSimple(int* array, int min1, int max1, int min2, int max2){
        mergeSort(array, min1, max1, submergeSortSimple);
        mergeSort(array, min2, max2, submergeSortSimple);
    }

    //function that swaps submergeSortSimple(), while sorting with threads
    void submergeSortThread(int* array, int min1, int max1, int min2, int max2){
        if(sem_trywait(sem_c)==-1){ //if there is already max_threads, program continues with simple sorting
            mergeSort(array, min1, max1, submergeSortSimple);
            mergeSort(array, min2, max2, submergeSortSimple);
            return;
        }
            thread_args arg1;
            arg1.array = array;
            arg1.min = min1;
            arg1.max = max1;
            arg1.function = submergeSortThread;
            pthread_t tid1;
            pthread_create(&tid1, 0, &thread_fun, &arg1);

            if(sem_trywait(sem_c)==-1){
                pthread_join(tid1,NULL);
                mergeSort(array, min2, max2, submergeSortSimple);
                return;
            }
            pthread_t tid2;
            thread_args arg2;
            arg2.array = array;
            arg2.min = min2;
            arg2.max = max2;
            arg2.function = submergeSortThread;
            pthread_create(&tid2, 0, &thread_fun, &arg2);
            pthread_join(tid1,NULL);
            pthread_join(tid2,NULL);
            return;            
    }



    // must not be changed
    void mergeSort(int *array, int min, int max, void(*submergeSort)(int *, int, int, int, int) ){
        int mid;
        if(min < max){
            mid=(min+max)/2;
            submergeSort(array, min, mid, mid+1, max);

            merge(array, min, mid, max);
        }
    }

    // must not be changed
    void merge(int *arr, int min,int mid,int max)
    {
        // drugi korak algoritma mergeSort
        int *tmp = malloc((max-min+1)*sizeof(int));
        int i,j,k,m;
        j=min;
        m=mid+1;

        for(i=min; j<=mid && m<=max ; i++)
        {
            if(arr[j]<=arr[m])
            {
                tmp[i-min]=arr[j];
                j++;
            }        
            else
            {
                tmp[i-min]=arr[m];
                m++;
            }
        }
        if(j>mid)
        {
            for(k=m; k<=max; k++)
            {
                tmp[i-min]=arr[k];
                i++;
            }
        }
        else
        {
            for(k=j; k<=mid; k++)
            {
                tmp[i-min]=arr[k];
                i++;
            }
        }
        for(k=min; k<=max; k++){
            arr[k]=tmp[k-min];
        }
        free(tmp);
    }

    int main(int argc, char *argv[])
    {
        #define NO_PAR 0 
        #define THREAD_PAR 2
        int technique= NO_PAR;
        void (*submergeSortFun)(int *, int, int, int, int);
        submergeSortFun = submergeSortSimple;
        while(1){
            int c;
            c = getopt(argc, argv, "ptn:");
            if(c==-1){
                break;
            }
            switch(c){

                case 't':
                    technique = THREAD_PAR;
                    submergeSortFun = submergeSortThread;
                    break;
                case 'n':
                    max_paralelizacij = atoi(optarg);                  
                    break;
                default:
                    printHelp(argc, argv);
                    return 0;
            }
        }
        if(technique == THREAD_PAR){
            sem_unlink("/C\0");
            sem_c = sem_open("/C", O_RDWR|O_CREAT|O_EXCL, 0660, max_paralelizacij);
            if(sem_c == SEM_FAILED){
                perror("Napaka pri ustvarjanju semaforja!\n");
            }
        }

        int i;
        int size;
        int *arr;
        if(optind >= argc){
            printHelp(argc, argv);
            return -1;
        }

        size = atoi(argv[optind]);


        // TODO: inicializacija za razlicne tehnike 
        switch(technique){
            case NO_PAR:
                arr = malloc(sizeof(int)*size);
                break;

            case THREAD_PAR:
                arr = malloc(sizeof(int)*size);
                break;
        }
        char buffer[101];

        // reading numbers from file
        for(i=0; i < size; i+=1){
            read(0, &arr[i], 4);
        }

        //measure time and sorting
        clock_t start = clock();
        mergeSort(arr, 0, size-1, submergeSortFun);
        clock_t end = clock();
        float seconds = (float)(end - start) / CLOCKS_PER_SEC;


        for(i=0; i<size; i++){

            printf("%d ",arr[i]);

        }
        printf("\n");



        // TODO: ciscenje za razlicnimi tehnikami
        switch(technique){
            case NO_PAR:
                free(arr);
                sem_close(sem_c);
                break;
            case THREAD_PAR:
                free(arr);
                sem_close(sem_c);
                break;
        }
                printf("\n\n CAS : %f\n\n",seconds);

        return 0;
    }

1 个答案:

答案 0 :(得分:0)

让我们观察一下创建线程与递归调用函数的成本。

创建线程时,将为该线程创建并初始化单独的调用堆栈程序计数器

调用函数时,它会添加到当前调用堆栈的顶部。这是单个呼叫单个条目。不需要单独的程序计数器

很显然,创建线程的开销远比调用函数大。因此,尽管两个实现的运行时间顺序相同,但由于创建线程的开销很大,线程化通常较慢。使用线程的合理情况是分配大量工作量,然后对较小的数组运行递归案例,以防止调用堆栈的大小爆炸。