[英]Why is my parallel code slower than serial?
問題
大家好,我有一個程序(來自網絡),我打算通過使用pthreads
將其轉換為並行版本來加速。 但令人驚訝的是,它的運行速度比串行版本慢。 下面是程序:
# include <stdio.h>
//fast square root algorithm
double asmSqrt(double x)
{
__asm__ ("fsqrt" : "+t" (x));
return x;
}
//test if a number is prime
bool isPrime(int n)
{
if (n <= 1) return false;
if (n == 2) return true;
if (n%2 == 0) return false;
int sqrtn,i;
sqrtn = asmSqrt(n);
for (i = 3; i <= sqrtn; i+=2) if (n%i == 0) return false;
return true;
}
//number generator iterated from 0 to n
int main()
{
n = 1000000; //maximum number
int k,j;
for (j = 0; j<= n; j++)
{
if(isPrime(j) == 1) k++;
if(j == n) printf("Count: %d\n",k);
}
return 0;
}
第一次嘗試並行化
我讓pthread
管理for loop
# include <stdio.h>
.
.
int main()
{
.
.
//----->pthread code here<----
for (j = 0; j<= n; j++)
{
if(isPrime(j) == 1) k++;
if(j == n) printf("Count: %d\n",k);
}
return 0;
}
嗯,它比串行運行慢
第二次嘗試
我將for loop
分成兩個線程並使用pthreads
並行運行它們
但是,它的運行速度仍然較慢,我打算將其運行速度提高兩倍或更快。 但它不是!
順便說一下,這些是我的並行代碼:
# include <stdio.h>
# include <pthread.h>
# include <cmath>
# define NTHREADS 2
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int k = 0;
double asmSqrt(double x)
{
__asm__ ("fsqrt" : "+t" (x));
return x;
}
struct arg_struct
{
int initialPrime;
int nextPrime;
};
bool isPrime(int n)
{
if (n <= 1) return false;
if (n == 2) return true;
if (n%2 == 0) return false;
int sqrtn,i;
sqrtn = asmSqrt(n);
for (i = 3; i <= sqrtn; i+=2) if (n%i == 0) return false;
return true;
}
void *parallel_launcher(void *arguments)
{
struct arg_struct *args = (struct arg_struct *)arguments;
int j = args -> initialPrime;
int n = args -> nextPrime - 1;
for (j = 0; j<= n; j++)
{
if(isPrime(j) == 1)
{
printf("This is prime: %d\n",j);
pthread_mutex_lock( &mutex1 );
k++;
pthread_mutex_unlock( &mutex1 );
}
if(j == n) printf("Count: %d\n",k);
}
pthread_exit(NULL);
}
int main()
{
int f = 100000000;
int m;
pthread_t thread_id[NTHREADS];
struct arg_struct args;
int rem = (f+1)%NTHREADS;
int n = floor((f+1)/NTHREADS);
for(int h = 0; h < NTHREADS; h++)
{
if(rem > 0)
{
m = n + 1;
rem-= 1;
}
else if(rem == 0)
{
m = n;
}
args.initialPrime = args.nextPrime;
args.nextPrime = args.initialPrime + m;
pthread_create(&thread_id[h], NULL, ¶llel_launcher, (void *)&args);
pthread_join(thread_id[h], NULL);
}
// printf("Count: %d\n",k);
return 0;
}
注意:操作系統:Fedora 21 x86_64,編譯器:gcc-4.4,處理器:Intel Core i5(2 個物理核心,4 個邏輯核心),內存:6 Gb,硬盤:340 Gb,
您需要將要檢查的素數范圍拆分為n部分,其中n是線程數。
每個線程運行的代碼變為:
typedef struct start_end {
int start;
int end;
} start_end_t;
int find_primes_in_range(void *in) {
start_end_t *start_end = (start_end_t *) in;
int num_primes = 0;
for (int j = start_end->start; j <= start_end->end; j++) {
if (isPrime(j) == 1)
num_primes++;
}
pthread_exit((void *) num_primes;
}
該main
程序首先啟動的所有線程調用哪個find_primes_in_range
,然后調用pthread_join
為每個線程。 它對find_primes_in_range
返回的所有值find_primes_in_range
。 這避免了鎖定和解鎖共享計數變量。
這將並行化工作,但每個線程的工作量將不相等。 這可以解決,但更復雜。
主要設計缺陷:您必須讓每個線程都有自己的私有計數器變量,而不是使用共享的計數器變量。 否則,他們將花費更多的時間等待和處理該互斥鎖,而不是實際計算。 您實際上是在強制線程串行執行。
取而代之的是,使用私有計數器變量將所有內容匯總,一旦線程完成其工作,返回計數器變量並在 main() 中匯總它們。
此外,您不應從線程內部調用 printf()。 如果在 printf 調用中間有上下文切換,您最終會得到蹩腳的輸出,例如This is This is prime: 2
。 在這種情況下,您必須同步線程之間的 printf 調用,這將再次減慢程序的速度。 此外, printf() 調用本身可能占線程正在執行的工作的 90%。 因此,對誰進行打印進行某種重新設計可能是一個好主意,這取決於您想對結果做什么。
概括
確實,使用 PThread 加快了我的代碼。 這是我在第一個pthread_create
和我在參數上設置的公共計數器之后立即放置pthread_join
編程缺陷。 解決這個問題后,我測試了我的並行代碼以確定 1 億個數字的素數,然后將其處理時間與串行代碼進行比較。 以下是結果。
http://i.stack.imgur.com/gXFyk.jpg (我無法附上圖片,因為我還沒有多少聲譽,相反,我提供了一個鏈接)
我對每個試驗進行了三項試驗,以解釋由不同操作系統活動引起的變化。 我們加快了使用PThread
並行編程的PThread
。 令人驚訝的是,在 ONE 線程中運行的PThread
代碼比純串行代碼快一點。 我無法解釋這個,不過使用PThreads
很好,當然值得一試。
這是代碼的更正並行版本(gcc-c++):
# include <stdio.h>
# include <pthread.h>
# include <cmath>
# define NTHREADS 4
double asmSqrt(double x)
{
__asm__ ("fsqrt" : "+t" (x));
return x;
}
struct start_end_f
{
int start;
int end;
};
//test if a number is prime
bool isPrime(int n)
{
if (n <= 1) return false;
if (n == 2) return true;
if (n%2 == 0) return false;
int sqrtn = asmSqrt(n);
for (int i = 3; i <= sqrtn; i+=2) if (n%i == 0) return false;
return true;
}
//executes the tests for prime in a certain range, other threads will test the next range and so on..
void *find_primes_in_range(void *in)
{
int k = 0;
struct start_end_f *start_end_h = (struct start_end_f *)in;
for (int j = start_end_h->start; j < (start_end_h->end +1); j++)
{
if(isPrime(j) == 1) k++;
}
int *t = new int;
*t = k;
pthread_exit(t);
}
int main()
{
int f = 100000000; //maximum number to be tested for prime
pthread_t thread_id[NTHREADS];
struct start_end_f start_end[NTHREADS];
int rem = (f+1)%NTHREADS;
int n = (f+1)/NTHREADS;
int rem_change = rem;
int m;
if(rem>0) m = n+1;
else if(rem == 0) m = n;
//distributes task 'evenly' to the number of parallel threads requested
for(int h = 0; h < NTHREADS; h++)
{
if(rem_change > 0)
{
start_end[h].start = m*h;
start_end[h].end = start_end[h].start+m-1;
rem_change -= 1;
}
else if(rem_change<= 0)
{
start_end[h].start = m*(h+rem_change)-rem_change*n;
start_end[h].end = start_end[h].start+n-1;
rem_change -= 1;
}
pthread_create(&thread_id[h], NULL, find_primes_in_range, &start_end[h]);
}
//retreiving returned values
int *t;
int c = 0;
for(int h = 0; h < NTHREADS; h++)
{
pthread_join(thread_id[h], (void **)&t);
int b = *((int *)t);
c += b;
b = 0;
}
printf("\nNumber of Primes: %d\n",c);
return 0;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.