我为OS课程作业写了这篇文章,我已经完成并递交了。我昨天发布了这个问题,但是由于"学术诚实"规则我把它推迟到提交截止日期之后。
目标是学习如何使用关键部分。有一个data
数组有100个单调递增的数字,0 ... 99和40个线程,每个线程随机交换两个元素2,000,000次。每隔一个Checker
经过一次并确保每个数字中只有一个(这意味着没有发生并行访问)。
以下是Linux时代:
real 0m5.102s
user 0m5.087s
sys 0m0.000s
和OS X次
real 6m54.139s
user 0m41.873s
sys 6m43.792s
我在运行OS X的同一台机器上运行一个带有ubuntu/trusty64
的流浪盒。它是一个四核i7 2.3Ghz(高达3.2Ghz)2012 rMBP。
如果我理解正确,sys
是系统开销,我无法控制,即便如此,41s的用户时间表明线程可能是串行运行的。
如果需要,我可以发布所有代码,但我会发布我认为相关的位。我正在使用pthreads
,因为这是Linux提供的,但我认为它们适用于OS X.
创建swapper
个线程以运行swapManyTimes
例程:
for (int i = 0; i < NUM_THREADS; i++) {
int err = pthread_create(&(threads[i]), NULL, swapManyTimes, NULL);
}
Swapper
线程关键部分,在for循环中运行200万次:
pthread_mutex_lock(&mutex); // begin critical section
int tmpFirst = data[first];
data[first] = data[second];
data[second] = tmpFirst;
pthread_mutex_unlock(&mutex); // end critical section
仅创建一个Checker
线程,与Swapper
相同。它通过遍历data
数组并使用true
标记与每个值对应的索引来操作。之后,它会检查有多少索引为空。就这样:
pthread_mutex_lock(&mutex);
for (int i = 0; i < DATA_SIZE; i++) {
int value = data[i];
consistency[value] = 1;
}
pthread_mutex_unlock(&mutex);
它在sleep(1)
循环运行后调用while(1)
,每秒运行一次。在加入所有swapper
个线程之后,该线程也会被取消并加入。
我很乐意提供更多信息,以帮助弄清楚为什么在Mac上这么糟糕。我并不是在寻求代码优化方面的帮助,除非这是摧毁OS X的原因。我已尝试使用clang
和{{1}构建它在OS X上。
答案 0 :(得分:6)
MacOSX和Linux以不同方式实现pthread,导致这种缓慢的行为。具体来说,MacOSX不使用自旋锁(根据ISO C标准,它们是可选的)。使用像这样的示例,这可能会导致非常非常慢的代码性能。
答案 1 :(得分:5)
我已经很好地复制了你的结果(没有清扫工):
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t Lock;
pthread_t LastThread;
int Array[100];
void *foo(void *arg)
{
pthread_t self = pthread_self();
int num_in_row = 1;
int num_streaks = 0;
double avg_strk = 0.0;
int i;
for (i = 0; i < 1000000; ++i)
{
int p1 = (int) (100.0 * rand() / (RAND_MAX - 1));
int p2 = (int) (100.0 * rand() / (RAND_MAX - 1));
pthread_mutex_lock(&Lock);
{
int tmp = Array[p1];
Array[p1] = Array[p2];
Array[p2] = tmp;
if (pthread_equal(LastThread, self))
++num_in_row;
else
{
++num_streaks;
avg_strk += (num_in_row - avg_strk) / num_streaks;
num_in_row = 1;
LastThread = self;
}
}
pthread_mutex_unlock(&Lock);
}
fprintf(stdout, "Thread exiting with avg streak length %lf\n", avg_strk);
return NULL;
}
int main(int argc, char **argv)
{
int num_threads = (argc > 1 ? atoi(argv[1]) : 40);
pthread_t thrs[num_threads];
void *ret;
int i;
if (pthread_mutex_init(&Lock, NULL))
{
perror("pthread_mutex_init failed!");
return 1;
}
for (i = 0; i < 100; ++i)
Array[i] = i;
for (i = 0; i < num_threads; ++i)
if (pthread_create(&thrs[i], NULL, foo, NULL))
{
perror("pthread create failed!");
return 1;
}
for (i = 0; i < num_threads; ++i)
if (pthread_join(thrs[i], &ret))
{
perror("pthread join failed!");
return 1;
}
/*
for (i = 0; i < 100; ++i)
printf("%d\n", Array[i]);
printf("Goodbye!\n");
*/
return 0;
}
在Linux(2.6.18-308.24.1.el5)服务器上英特尔(R)Xeon(R)CPU E3-1230 V2 @ 3.30GHz
[ltn@svg-dc60-t1 ~]$ time ./a.out 1
real 0m0.068s
user 0m0.068s
sys 0m0.001s
[ltn@svg-dc60-t1 ~]$ time ./a.out 2
real 0m0.378s
user 0m0.443s
sys 0m0.135s
[ltn@svg-dc60-t1 ~]$ time ./a.out 3
real 0m0.899s
user 0m0.956s
sys 0m0.941s
[ltn@svg-dc60-t1 ~]$ time ./a.out 4
real 0m1.472s
user 0m1.472s
sys 0m2.686s
[ltn@svg-dc60-t1 ~]$ time ./a.out 5
real 0m1.720s
user 0m1.660s
sys 0m4.591s
[ltn@svg-dc60-t1 ~]$ time ./a.out 40
real 0m11.245s
user 0m13.716s
sys 1m14.896s
在我的MacBook Pro(Yosemite 10.10.2)2.6 GHz i7,16 GB内存
上john-schultzs-macbook-pro:~ jschultz$ time ./a.out 1
real 0m0.057s
user 0m0.054s
sys 0m0.002s
john-schultzs-macbook-pro:~ jschultz$ time ./a.out 2
real 0m5.684s
user 0m1.148s
sys 0m5.353s
john-schultzs-macbook-pro:~ jschultz$ time ./a.out 3
real 0m8.946s
user 0m1.967s
sys 0m8.034s
john-schultzs-macbook-pro:~ jschultz$ time ./a.out 4
real 0m11.980s
user 0m2.274s
sys 0m10.801s
john-schultzs-macbook-pro:~ jschultz$ time ./a.out 5
real 0m15.680s
user 0m3.307s
sys 0m14.158s
john-schultzs-macbook-pro:~ jschultz$ time ./a.out 40
real 2m7.377s
user 0m23.926s
sys 2m2.434s
我的Mac花了大约12倍的挂钟时间来完成40个线程,而这是一个非常旧版本的Linux + gcc。
注意:我将代码更改为每个线程进行1M交换。
看起来在竞争中,OSX比Linux做了更多的工作很多。也许它比Linux更好地交错呢?
编辑更新了代码,以记录线程立即重新捕获锁定的平均次数:
Linux的
[ltn@svg-dc60-t1 ~]$ time ./a.out 10
Thread exiting with avg streak length 2.103567
Thread exiting with avg streak length 2.156641
Thread exiting with avg streak length 2.101194
Thread exiting with avg streak length 2.068383
Thread exiting with avg streak length 2.110132
Thread exiting with avg streak length 2.046878
Thread exiting with avg streak length 2.087338
Thread exiting with avg streak length 2.049701
Thread exiting with avg streak length 2.041052
Thread exiting with avg streak length 2.048456
real 0m2.837s
user 0m3.012s
sys 0m16.040s
Mac OSX
john-schultzs-macbook-pro:~ jschultz$ time ./a.out 10
Thread exiting with avg streak length 1.000000
Thread exiting with avg streak length 1.000000
Thread exiting with avg streak length 1.000000
Thread exiting with avg streak length 1.000000
Thread exiting with avg streak length 1.000000
Thread exiting with avg streak length 1.000000
Thread exiting with avg streak length 1.000000
Thread exiting with avg streak length 1.000000
Thread exiting with avg streak length 1.000000
Thread exiting with avg streak length 1.000000
real 0m34.163s
user 0m5.902s
sys 0m30.329s
因此,OSX更均匀地共享其锁,因此有更多的线程暂停和恢复。
答案 2 :(得分:1)
The OP does not mention/show any code that indicates the thread(s) sleep, wait, give up execution, etc and all the threads are at the same 'nice' level.
所以一个单独的线程可能会获得CPU,并且在完成所有2mil执行之前不会释放它。
这将导致在Linux上执行上下文切换的时间最短。
然而,在MAC OS上,执行仅被给予时间片&#39;在另一个准备执行之前执行#39;允许线程/进程执行。
这意味着更多的上下文切换。
上下文切换在&#39; sys&#39;中执行。时间。
结果是MAC OS将花费更长的时间来执行。
对于游戏领域,您可以通过插入nanosleep()或调用来释放执行来强制进行上下文切换
#include <sched.h>
then calling
int sched_yield(void);