Многопоточная программа C в OS X намного медленнее, чем Linux
Я написал это для назначения класса ОС, которое я уже выполнил и передал. Вчера я опубликовал этот вопрос, но из-за правил "Академической честности" я снял его до истечения срока подачи заявки.
Целью было научиться использовать критические разделы. Существует массив data
с 100 монотонно увеличивающимися числами, 0... 99 и 40 потоками, которые случайным образом меняют местами два элемента по 20000 раз каждый. Через секунду проходит a Checker
и удостоверяется, что есть только одно из каждого числа (что означает, что параллельный доступ не произошел).
Здесь были времена Linux:
real 0m5.102s
user 0m5.087s
sys 0m0.000s
и OS X раз
real 6m54.139s
user 0m41.873s
sys 6m43.792s
Я запускаю бродячий ящик с ubuntu/trusty64
на том же компьютере, на котором работает OS X. Это четырехъядерный i7 2.3Ghz (до 3.2 ГГц) 2012 rMBP.
Если я правильно понимаю, sys
- это системные издержки, над которыми я не контролирую, и даже тогда 41 с пользовательского времени предполагает, что потоки выполняются серийно.
Я могу опубликовать весь код, если это необходимо, но я опубликую бит, которые, по моему мнению, актуальны. Я использую 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 2 миллиона раз:
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
и gcc-4.9
в OS X.
Ответы
Ответ 1
MacOSX и Linux реализуют pthread по-разному, вызывая это медленное поведение. В частности, MacOSX не использует винтовые блоки (они являются необязательными в соответствии со стандартом ISO C). Это может привести к очень медленной работе кода с примерами, подобными этому.
Ответ 2
Я продублировал ваш результат в значительной степени (без подметания):
#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) Intel (R) Xeon (R) CPU E3-1230 V2 @3,30 ГГц
[[email protected] ~]$ time ./a.out 1
real 0m0.068s
user 0m0.068s
sys 0m0.001s
[[email protected] ~]$ time ./a.out 2
real 0m0.378s
user 0m0.443s
sys 0m0.135s
[[email protected] ~]$ time ./a.out 3
real 0m0.899s
user 0m0.956s
sys 0m0.941s
[[email protected] ~]$ time ./a.out 4
real 0m1.472s
user 0m1.472s
sys 0m2.686s
[[email protected] ~]$ time ./a.out 5
real 0m1.720s
user 0m1.660s
sys 0m4.591s
[[email protected] ~]$ time ./a.out 40
real 0m11.245s
user 0m13.716s
sys 1m14.896s
На моем MacBook Pro (Yosemite 10.10.2) 2.6 ГГц i7, 16 ГБ памяти
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
Потребовалось примерно в 12 раз больше времени на настенные часы, чтобы выполнить 40 потоков, и это по сравнению с очень старой версией Linux + gcc.
ПРИМЕЧАНИЕ. Я изменил свой код, чтобы сделать 1M свопов в потоке.
Похоже, что в конфликте OSX делает много больше работы, чем Linux. Может быть, это чередуется с ними намного лучше, чем Linux?
EDIT Обновленный код для записи avg количества раз, когда поток немедленно захватывает блокировку:
Linux
[[email protected] ~]$ 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 делится своими блокировками гораздо более равномерно и, следовательно, имеет еще множество приостановок и повторений потоков.
Ответ 3
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.
поэтому отдельный поток может получить процессор и не выпускать его до тех пор, пока он не завершит выполнение всех 2mil.
Это приведет к минимальному времени выполнения контекстных переключателей в linux.
Тем не менее, в ОС MAC для выполнения предоставляется только "временной срез" для выполнения, прежде чем разрешено выполнение "готового к исполнению" потока/процесса.
Это означает гораздо больше контекстных переключателей.
Контекстные коммутаторы выполняются в режиме "sys".
В результате MAC-OS займет гораздо больше времени.
Для игрового поля вы можете принудительно переключать контекстные переключатели, вставляя нанослое() или вызов, чтобы освободить выполнение через
#include <sched.h>
then calling
int sched_yield(void);