Linux 线程化实现会降低性能
我用C实现了一个小程序,用蒙特卡罗方法计算PI(主要是因为个人兴趣和培训)。在实现了基本的代码结构之后,我添加了一个命令行选项,允许执行线程计算 我期待着大幅提速,但我失望了。命令行概要应该很清楚。为近似PI而进行的最终迭代次数是通过命令行传递的Linux 线程化实现会降低性能,linux,windows,performance,pthreads,Linux,Windows,Performance,Pthreads,我用C实现了一个小程序,用蒙特卡罗方法计算PI(主要是因为个人兴趣和培训)。在实现了基本的代码结构之后,我添加了一个命令行选项,允许执行线程计算 我期待着大幅提速,但我失望了。命令行概要应该很清楚。为近似PI而进行的最终迭代次数是通过命令行传递的-iterations和-threads次数的乘积。将-threads保留为空默认为1thread,从而在主线程中执行 下面的测试总共进行了8000万次迭代 在Windows 7 64位(英特尔Core2Duo计算机)上: 使用Cygwin GCC 4
-iterations
和-threads
次数的乘积。将-threads
保留为空默认为1
thread,从而在主线程中执行
下面的测试总共进行了8000万次迭代
在Windows 7 64位(英特尔Core2Duo计算机)上:
使用Cygwin GCC 4.5.3编译:GCC-4 pi.c-o pi.exe-O3
在Ubuntu/Linaro 12.04(8Core AMD)上:
使用GCC4.6.3编译:GCC pi.c-lm-lpthread-O3-o pi
演出
在Windows上,线程版本比非线程版本快几毫秒。老实说,我期望有更好的表现。在Linux上,ew!搞什么鬼?为什么还要花2000%的时间?当然,这在很大程度上取决于实现情况,所以就这样。完成命令行参数分析并开始计算后的摘录:
// Begin computation.
clock_t t_start, t_delta;
double pi = 0;
if (args.threads == 1) {
t_start = clock();
pi = pi_mc(args.iterations);
t_delta = clock() - t_start;
}
else {
pthread_t* threads = malloc(sizeof(pthread_t) * args.threads);
if (!threads) {
return alloc_failed();
}
struct PIThreadData* values = malloc(sizeof(struct PIThreadData) * args.threads);
if (!values) {
free(threads);
return alloc_failed();
}
t_start = clock();
for (i=0; i < args.threads; i++) {
values[i].iterations = args.iterations;
values[i].out = 0.0;
pthread_create(threads + i, NULL, pi_mc_threaded, values + i);
}
for (i=0; i < args.threads; i++) {
pthread_join(threads[i], NULL);
pi += values[i].out;
}
t_delta = clock() - t_start;
free(threads);
threads = NULL;
free(values);
values = NULL;
pi /= (double) args.threads;
}
您可以在找到完整的源代码
问题:
为什么会这样?为什么Linux上会出现这种极端的差异?我预计计算所需的时间至少是原始时间的3/4。当然,我可能只是错误地使用了
pthread
库。在这种情况下,澄清如何进行纠正是非常好的。性能和线程是一门黑色艺术。答案取决于编译器和用于执行线程处理的库的具体情况,以及内核处理它的能力等。基本上,如果*nix库在切换、移动对象等方面效率不高,那么线程处理实际上会变慢。这就是我们现在使用JVM或类似JVM的语言进行线程工作的原因之一。我们可以信任运行时JVM的行为——它的总体速度可能因平台而异,但在该平台上是一致的。此外,您可能有一些隐藏的等待/竞态条件,这些条件可能是由于时间原因而未在Windows上显示的
如果你有能力改变语言,考虑Scala或D. Scala是java驱动的模型继承者,D是C语言的继承者,显示出他们的根——如果你能用C语言写,D应该没问题。但是,这两种语言都实现了actor模型。没有更多的线程池,没有更多的比赛条件等
问题在于,在glibc的实现中,rand()
调用
long int
__random ()
{
int32_t retval;
__libc_lock_lock (lock);
(void) __random_r (&unsafe_state, &retval);
__libc_lock_unlock (lock);
return retval;
}
锁定对执行实际工作的函数\uu random\r
的每次调用
因此,当您使用
rand()
有多个线程时,几乎每次调用rand()
时,都会让每个线程等待其他线程。在每个线程中,直接使用<代码> RealthRead()/Cuffe,您自己的缓冲区应该快得多。 < P>比较,我刚尝试了Windows Vista上的应用程序,用Borland C++编译,2线程版本执行的速度几乎是单线程的两倍。
pi.exe -iterations 20000000 -stats -threads 1
3.141167
Number of iterations: 20000000
Method: Monte Carlo
Evaluation time: 12.511000 sec
Threads: Main
pi.exe -iterations 10000000 -stats -threads 2
3.142397
Number of iterations: 20000000
Method: Monte Carlo
Evaluation time: 6.584000 sec
Threads: 2
这是根据线程安全运行时库编译的。使用单线程库,两个版本的运行速度都是其线程安全速度的两倍
pi.exe -iterations 20000000 -stats -threads 1
3.141167
Number of iterations: 20000000
Method: Monte Carlo
Evaluation time: 6.458000 sec
Threads: Main
pi.exe -iterations 10000000 -stats -threads 2
3.141314
Number of iterations: 20000000
Method: Monte Carlo
Evaluation time: 3.978000 sec
Threads: 2
因此,双线程版本的速度仍然是双线程版本的两倍,但是带有单线程库的单线程版本实际上比线程安全库上的双线程版本快
看看Borland的rand实现,他们在线程安全实现中使用线程本地存储作为种子,因此不会像glibc的锁那样对线程代码产生负面影响,但是线程安全实现显然比单线程实现慢
但底线是,在这两种情况下,编译器的rand
实现可能是主要的性能问题
更新
我刚刚尝试将您的rand\u 01
调用替换为Borland的rand
函数的内联实现,该函数使用一个局部变量作为种子,并且在双线程情况下,结果的速度始终是原来的两倍
更新后的代码如下所示:
#define MULTIPLIER 0x015a4e35L
#define INCREMENT 1
double pi_mc(int iterations) {
unsigned seed = 1;
long long inner = 0;
long long outer = 0;
int i;
for (i=0; i < iterations; i++) {
seed = MULTIPLIER * seed + INCREMENT;
double x = ((int)(seed >> 16) & 0x7fff) / (double) RAND_MAX;
seed = MULTIPLIER * seed + INCREMENT;
double y = ((int)(seed >> 16) & 0x7fff) / (double) RAND_MAX;
double d = sqrt(pow(x, 2.0) + pow(y, 2.0));
if (d <= 1.0) {
inner++;
}
else {
outer++;
}
}
return ((double) inner / (double) iterations) * 4;
}
#定义乘法器0x015a4e35L
#定义增量1
双pi_mc(整数迭代){
无符号种子=1;
长内=0;
长外=0;
int i;
对于(i=0;i>16)和0x7fff)/(双)RAND_MAX;
种子=乘数*种子+增量;
双y=((int)(seed>>16)和0x7fff)/(双)RAND_MAX;
双d=sqrt(功率(x,2.0)+功率(y,2.0));
如果(d)
#define MULTIPLIER 0x015a4e35L
#define INCREMENT 1
double pi_mc(int iterations) {
unsigned seed = 1;
long long inner = 0;
long long outer = 0;
int i;
for (i=0; i < iterations; i++) {
seed = MULTIPLIER * seed + INCREMENT;
double x = ((int)(seed >> 16) & 0x7fff) / (double) RAND_MAX;
seed = MULTIPLIER * seed + INCREMENT;
double y = ((int)(seed >> 16) & 0x7fff) / (double) RAND_MAX;
double d = sqrt(pow(x, 2.0) + pow(y, 2.0));
if (d <= 1.0) {
inner++;
}
else {
outer++;
}
}
return ((double) inner / (double) iterations) * 4;
}