我编写了一个用于合并排序的程序,该程序使用多线程和多处理对随机整数进行排序。
这是我的代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>
#include <wait.h>
#include <semaphore.h>
#include <stdbool.h>
#include <getopt.h>
// #include <sys/stat.h>
// #include <sys/syscall.h>
// #include <sys/types.h>
int stNiti = 0;
int *stProcesov = 0;
int prestejNiti = 0;
int prestejProcese = 0;
int stMax = 500; //privzeto stevilo niti/procesov
pthread_t *threadArr;
sem_t *lockThread;
sem_t *lockProc;
struct threadArg{
pthread_t id;
int *array;
int min;
int max;
void (*submergeSortHelper)(int*, int, int, int, int);
} ;
//typedef submergeSortArg
// int sestej(int *arr, int len, int skok, int zacetna_vrednost) {
//
// }
bool checkArray(int* array, int size);
void printHelp(int argc, char** argv);
void submergeSortSimple(int* array, int min1, int max1, int min2, int max2);
void submergeSortProc(int* array, int min1, int max1, int min2, int max2);
void submergeSortThread(int* array, int min1, int max1, int min2, int max2);
void mergeSort(int *array, int min, int max, void(*submergeSort)(int *, int, int, int, int));
void merge(int *arr,int min,int mid,int max);
// preprosta implementacija mergeSort rekurzije,
// samo klicemo margeSort za levo in desno polovico
// v istem procesu/isti niti
void submergeSortSimple(int* array, int min1, int max1, int min2, int max2){
mergeSort(array, min1, max1, submergeSortSimple);
mergeSort(array, min2, max2, submergeSortSimple);
}
void* thread_fun(void *args) {
struct threadArg *arg = (struct threadArg*) args;
mergeSort(arg->array, arg->min, arg->max, arg->submergeSortHelper);
}
// TODO: funkcija ki paralelizira sortiranje z uporabo procesov
// za preprosto paralelizacijo samo izvedemo vsak klic mergeSort
// funkcije v svojem procesu, in počakamo, da se klica zaključita
void submergeSortProc(int* array, int min1, int max1, int min2, int max2){
int index = 0;
if(*stProcesov <= stMax-1) {
sem_wait(lockProc);
*stProcesov+=1;
sem_post(lockProc);
// printf("Proces %d \n", *stProcesov);
int proces = fork();
if(proces > 0){//stars
wait(0);
wait(0);
mergeSort(array,min2,max2,submergeSortProc);
}
else if(proces == -1){
perror("Napaka pri fork()!\n");
_exit(-1);
}
else{
mergeSort(array,min1,max1,submergeSortProc);
_exit(0);
}
}
else { //dosezen je maksimum stevila procesov
mergeSort(array, min1, max1, submergeSortSimple);
// sleep(1);
mergeSort(array, min2, max2, submergeSortSimple);
}
// stProcesov = index;
// return;
if(*stProcesov < stMax) {
prestejProcese = *stProcesov+1;
}
else {
prestejProcese = *stProcesov;
}
}
// TODO: funkcija, ki paralelizira sortiranje z uporabo niti
// za preprosto paralelizacijo samo izvedemo vsak klic mergeSort
// funkcije v svoji niti, in počakamo, da se klica zaključita
// void *helperThreadFunction(void *args);
void submergeSortThread(int* array, int min1, int max1, int min2, int max2){
int index = 0;
if(stNiti <= stMax-1) {
//sinhroinizacija - narediti semafor
sem_wait(lockThread);
index = stNiti;
stNiti++;
// printf("St niti: %d\n", stNiti);
sem_post(lockThread);
int statusC = 0, statusJ = 0; //statusa za join in create
struct threadArg prvi;
struct threadArg drugi;
prvi.array = array;
drugi.array = array;
prvi.max = max1;
drugi.max = max2;
prvi.min = min1;
drugi.min = min2;
prvi.submergeSortHelper = submergeSortThread;
drugi.submergeSortHelper = submergeSortThread;
//ustvarimo novo nit
statusC = pthread_create(&threadArr[index], 0, thread_fun, (void*)&prvi);
if(statusC != 0) {
perror("Napaka pri pthread_create");
_exit(-1);
}
statusJ = pthread_join(threadArr[index], 0);
if(statusJ != 0) {
perror("Napaka pri pthread_join");
_exit(-1);
}
thread_fun((void*)&drugi);
// pthread_t tid1, tid2;
//ustvarimo novi niti za mergeSort
// pthread_create(&tid1, 0, &thread_fun, &prvi);
// pthread_create(&tid2, 0, &thread_fun, &drugi);
// pthread_join(tid1, 0);
// pthread_join(tid2, 0);
}
else {
//maxNiti je dosezeno, preostanek uredimo z navadnim sortiranjem
struct threadArg prvi;
struct threadArg drugi;
prvi.array = array;
drugi.array = array;
prvi.max = max1;
drugi.max = max2;
prvi.min = min1;
drugi.min = min2;
prvi.submergeSortHelper = submergeSortThread;
drugi.submergeSortHelper = submergeSortThread;
thread_fun((void*)&prvi);
thread_fun((void*)&drugi);
}
// prestejNiti = stNiti; //stevec za niti
if(stNiti < stMax) {
prestejNiti = stNiti+1;
}
else {
prestejNiti = stNiti;
}
}
// mergeSort in merge funkciji
// ti dve izvajata dejansko sortiranje
// void mergeSort(int *array, int min, int max, void(*submergeSort)(int *, int, int, int, int) )
//
// int *array
// kazalec na tabelo števil, ki jih urejamo
//
// int min, int max
// indeks prvega in zadnjega števila v tabeli,
// označujeta interval, ki ga sortiramo
//
// void (*submergeSort)(int *array, int min1, int max1, int min2, int max2)
// kazalec na funkcijo, ki naj kliče mergeSort za dva podintervala
// in vrne, ko sta oba intervala sortirana
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);
}
}
// void merge(int *arr, int min,int mid,int max)
//
// int *arr
// kazalec na tabelo
//
// int min, int mid, int max
// indeksi na del tabele, ki jih je potrebno združiti
// min je začetni indeks prve podtabele, mid je zadnji indeks
// prve podtabele in max je zadnji indeks druge podtabele
//
// metoda zdruzi dve sosednji sortirani podtabeli,
// tako da je nova podtabela tudi sortirana
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[])
{
int i;
int size;
int *arr;
pthread_t threads[stMax];
int **temp;
#define NO_PAR 0
#define PROC_PAR 1
#define THREAD_PAR 2
int technique= NO_PAR;
int number;
char *value = NULL;
void (*submergeSortFun)(int *, int, int, int, int);
submergeSortFun = submergeSortSimple;
while(1){
int c;
c = getopt(argc, argv, "ptn");
if(c==-1){
// printf("!!!Negativno!!!");
break;
}
switch(c){
case 'p':
technique = PROC_PAR;
submergeSortFun = submergeSortProc;
stMax = atoi(argv[3]);
break;
case 't':
technique = THREAD_PAR;
submergeSortFun = submergeSortThread;
stMax = atoi(argv[3]);
printf("\nStMax: %d\n", stMax);
break;
case 'n':
size = atoi(argv[4]);
printf("\nSize: %d\n\n\n", size);
break;
default:
printHelp(argc, argv);
return 0;
}
}
for(int i=0; i<argc; i+=1) {
printf("argv[%d]: %s\n", i, argv[i]);
}
if(optind >= argc){
printHelp(argc, argv);
return -1;
}
//size = atoi(argv[optind]);
// size = atoi(argv[4]);
// TODO: inicializacija za razlicne tehnike
switch(technique){
case NO_PAR:
arr = malloc(sizeof(int)*size);
break;
case PROC_PAR:
// inicializacija za uporabo procesov
// ustvariti je potrebno deljen pomnilnik, semafor, ...
sem_unlink("lockProc");
lockProc = sem_open("lockProc", O_RDWR|O_CREAT|O_EXCL, 0666, 1);
stProcesov = mmap (0,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS, 0, 0);
arr=mmap (0,sizeof(int)*size,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS, 0, 0);
*stProcesov = 0;
break;
case THREAD_PAR:
// inicializacija za uporabo niti
// tukaj potrebujete morebitne sinhronizacijske strukture
arr = malloc(sizeof(int)*size);
sem_unlink("lockThread");
lockThread=sem_open("lockThread", O_RDWR|O_CREAT|O_EXCL, 0660, 1);
threadArr = threads;
break;
}
char buffer[101];
for(i=0; i < size; i+=1){
// preberi binarne vrednosti
read(0, &arr[i], 4);
}
int stevilo = 10000;
for(int i=0; i<1000; i+=1) {
arr[i] = stevilo;
stevilo-=2;
}
// printf("Pred urejanjem: ");
// for(i=0; i<size; i++){
// printf("%d ",arr[i]);
// }
// printf("\n");
mergeSort(arr, 0, size-1, submergeSortFun);
// printf("Po urejanju: ");
// for(i=0; i<size; i++){
// printf("%d ",arr[i]);
// }
printf("\n\n\n");
if(prestejNiti > 0 && prestejProcese == 0) {
printf("St. niti, ki so sodelovale: %d \n\n", prestejNiti);
}
else if(prestejProcese > 0 && prestejNiti == 0) {
printf("St. procesov, ki so sodelovali: %d\n\n", prestejProcese);
}
// TODO: ciscenje za razlicnimi tehnikami
switch(technique){
case NO_PAR:
free(arr);
break;
case PROC_PAR:
// *temp = stProcesov;
munmap(arr, sizeof(int)*size);
munmap(stProcesov, sizeof(int));
sem_close(lockProc);
sem_unlink("lockProc");
break;
case THREAD_PAR:
sem_close(lockThread);
sem_unlink("lockThread");
break;
}
return 0;
}
void printHelp(int argc, char** argv){
printf("uporaba\n");
printf("%s <opcija> <n>\n",argv[0]);
printf("\tn je število celih števil prebranih z standardnega vhoda\n");
printf("\tfunkcije prebere n*4 bajtov v tabelo in jih sortira\n");
printf("opcije:\n");
printf("-p\n");
printf("\tparalelizacija s pomočjo procesov\n");
printf("-t\n");
printf("\tparalelizacija s pomočjo niti\n");
}
现在,当我测量时间时,可以看到当我使用50或100个线程或进程时,它比仅使用一个线程花费更多的时间。 我使用Xubuntu,并使用终端进行测量(时间cat / dev / urandom | ./mergeSort -p -n 200 1000000000)
有人可以告诉我这是否正常,还是我在代码中的某个地方犯了错误。
谢谢。
答案 0 :(得分:5)
从根本上讲,需要完成的工作量始终是相同的。 将其视为大量的“待办事项”清单。即使您在多个线程甚至是多台计算机之间划分,工作列表上的所有项目仍需要完成才能使排序成功。
现在,考虑单核CPU的最简单情况。
即使它能够运行多个线程,该单核CPU现在仍需要完成必填任务列表中的所有内容,并且 还花时间安排线程并在它们之间进行切换。这肯定比仅一个线程全速运行的速度要慢。
如果您有多个核心,则可以在这些核心之间划分工作,每个核心都可以解决部分问题。划分工作并重新组合结果仍然存在一些开销。因此8核处理器的速度 不是 是8倍。它将比单核快6到7倍。
这仅适用于尽可能多的内核。如果现在尝试在8个内核之间调度100个线程,则每个内核大约管理12个线程!每个内核都花时间安排时间并在线程之间交换,并使高速缓存数据与其他内核保持一致。 您的CPU将负担繁重的工作,同时尝试获取原始的实际排序数据的强制性待办事项列表。
换句话说,您可以调度与内核一样多的线程,但不能调度更多。之后,您只需要为内核做更多的多线程工作即可,而无需再为解决实际问题增加强大的功能。
答案 1 :(得分:2)
因此,您想使用线程来加速mergesort。
基本思想:将数组切成与核心相同数量的块,并行合并。然后合并大块。由于您有两个以上的线程,因此也可以部分并行执行。
请不要产生比内核更多的线程来加速CPU。这适得其反。