为什么我的并行代码比串行代码慢?
Why is my parallel code slower than serial?
问题
大家好,我有一个程序(来自网络),我打算通过使用 pthreads
将其转换为并行版本来加速。但令人惊讶的是,它 运行s 比串行版本慢 。下面是程序:
# 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;
}
注:
OS: 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
返回的所有值求和。这避免了锁定和解锁共享计数变量。
这将使工作并行化,但每个线程的工作量将不相等。这可以解决,但更复杂。
主要设计缺陷:必须让每个线程都有自己的私有计数器变量,而不是使用共享的计数器变量。否则,他们将花费比实际计算更多的时间来等待和处理该互斥量。您实际上是在强制线程串行执行。
相反,使用私有计数器变量对所有内容进行总结,一旦线程完成其工作,return 计数器变量并在 main() 中对它们进行总结。
此外,您不应从线程内部调用 printf()。如果在 printf 调用的中间有一个上下文切换,您最终会得到糟糕的输出,例如 This is This is prime: 2
。在这种情况下,您必须同步线程之间的 printf 调用,这将再次降低程序速度。此外,printf() 调用本身可能占线程正在执行的工作的 90%。因此,重新设计由谁来打印可能是个好主意,具体取决于您要对结果做什么。
总结
的确,使用 PThread 加快了我的代码。将 pthread_join
放在第一个 pthread_create
和我在参数上设置的公共计数器之后是我的编程缺陷。修复此问题后,我测试了我的并行代码以确定 1 亿个数字的素数,然后将其处理时间与串行代码进行了比较。以下是结果。
http://i.stack.imgur.com/gXFyk.jpg(我无法附上图片,因为我还没有太多声誉,相反,我附上了 link)
我对每个进行了三项试验,以解释不同 OS 活动造成的差异。我们利用 PThread
的并行编程加快了速度。令人惊讶的是,一个线程中的 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;
}
问题
大家好,我有一个程序(来自网络),我打算通过使用 pthreads
将其转换为并行版本来加速。但令人惊讶的是,它 运行s 比串行版本慢 。下面是程序:
# 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;
}
注: OS: 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
返回的所有值求和。这避免了锁定和解锁共享计数变量。
这将使工作并行化,但每个线程的工作量将不相等。这可以解决,但更复杂。
主要设计缺陷:必须让每个线程都有自己的私有计数器变量,而不是使用共享的计数器变量。否则,他们将花费比实际计算更多的时间来等待和处理该互斥量。您实际上是在强制线程串行执行。
相反,使用私有计数器变量对所有内容进行总结,一旦线程完成其工作,return 计数器变量并在 main() 中对它们进行总结。
此外,您不应从线程内部调用 printf()。如果在 printf 调用的中间有一个上下文切换,您最终会得到糟糕的输出,例如 This is This is prime: 2
。在这种情况下,您必须同步线程之间的 printf 调用,这将再次降低程序速度。此外,printf() 调用本身可能占线程正在执行的工作的 90%。因此,重新设计由谁来打印可能是个好主意,具体取决于您要对结果做什么。
总结
的确,使用 PThread 加快了我的代码。将 pthread_join
放在第一个 pthread_create
和我在参数上设置的公共计数器之后是我的编程缺陷。修复此问题后,我测试了我的并行代码以确定 1 亿个数字的素数,然后将其处理时间与串行代码进行了比较。以下是结果。
http://i.stack.imgur.com/gXFyk.jpg(我无法附上图片,因为我还没有太多声誉,相反,我附上了 link)
我对每个进行了三项试验,以解释不同 OS 活动造成的差异。我们利用 PThread
的并行编程加快了速度。令人惊讶的是,一个线程中的 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;
}