【问题标题】:Why is my Merge Sort with threads slower than basic recursive Merge Sort? [duplicate]为什么我的线程合并排序比基本递归合并排序慢? [复制]
【发布时间】:2020-03-25 01:19:03
【问题描述】:

我是一名学生,我需要使用线程来实现 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;
    }

【问题讨论】:

  • 我们在谈论要排序的数据量是多少?使用线程会带来相当多的开销,线程必须使用其所有设施创建并在运行后销毁。如果有足够的数据,您将只能从多个线程中获利,这样加速可以补偿线程管理成本......
  • 好吧,10000 对于数组大小来说有点小,所以所有线程创建的开销可能超过实际排序,尝试使用更大的数组,例如 10000000
  • 我正在排序 100 1k 10k 100k 和 1M 数字,使用线程排序总是更慢,除了 1M 数字我得到 1-5ms 更快的排序。另一方面,使用 fork() 排序(进程)我可以更快地排序......我还在使用 4GB RAM 和 2 CPE 在 vi​​rtualbox 上运行程序,如果这会影响排序速度?
  • 题外话:atoi 通常是将字符串转换为整数的错误选择,因为您无法区分无效输入和合法提供的零值。在给定的情况下,我们可能会假设零是无效输入——但你至少应该检查(对于负值,也一样,因为你不使用 unsigned int...)。
  • 好吧,有了 1M,您终于通过了创建线程的成本被摊销的地步……在 POSIX 系统上,线程和进程实际上差别很小。与线程相比,使用 fork 进行排序不会给您带来任何加速除非您做了一些根本错误的事情。除此之外,这些不同的进程各自使用自己的内存(要排序的数据被复制),所以你很难重新组合结果......

标签: c multithreading algorithm mergesort


【解决方案1】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 2021-12-09
    • 2021-06-19
    • 2015-06-25
    • 2011-09-01
    • 2014-12-14
    • 2013-03-15
    • 2021-11-06
    • 2019-01-22
    相关资源
    最近更新 更多