• Весь объем этой работы делится поровну (или почти поровну) между несколькими (ключ
t
) потоками.
• Сравниваем усредненное время единичного выполнения рабочей функции для разного числа выполняющих потоков (в выводе
"calculating"
— это время эталонного вычисления в одном главном потоке, a
"evaluation"
— время того же вычисления, но во многих потоках).
• Для того чтобы иметь еще большую гибкость, предоставляется возможность переопределять приоритет, под которым в системе все это происходит (ключ
p
).
Вот самая краткая сводка результатов (1-я строка вывода переносится для удобства чтения):
# t1 -n1 -t1000 -a2000
Multi-thread evaluation, thread number = 1000, priority level: 10
rescheduling = 3.99939
calculating = 1.04144
evaluation = 1.08001
# t1 -n1 -t10000 -a20000
Multi-thread evaluation, thread number = 10000, priority level: 10
rescheduling = 3.99939
calculating = 1.04378
evaluation = 1.61946
# t1 -n5 -a2000 -t1
Single-thread evaluation, priority level: 10
rescheduling = 3.99939
calculating = 5.07326
evaluation = 5.04726
# t1 -n5 -a2000 -t2
Multi-thread evaluation, thread number = 2, priority level: 10
rescheduling = 3.99939
calculating = 5.06309
evaluation = 5.04649
# t1 -n5 -a2000 -t20
Multi-thread evaluation, thread number = 20, priority level: 10
rescheduling = 3.99939
calculating = 5.06343
evaluation = 4.96956
# t1 -n5 -p51 -a512 -t1
Single-thread evaluation, priority level: 51
rescheduling = 3.99939
calculating = 4.94502
evaluation = 4.94511
# t1 -n5 -р51 -a512 -t11
Multi-thread evaluation, thread number = 11, priority level: 51
rescheduling = 3.99939
calculating = 4.94554
evaluation = 4.94549
# t1 -n5 -p51 -a512 -t111
Multi-thread evaluation, thread number = 111, priority level: 51
rescheduling = 3.99939
calculating = 5.02755
evaluation = 4.94487
# t1 -n5 -p51 -a30000 -t10000
Multi-thread evaluation, thread number = 10000, priority level: 51
rescheduling = 3.99939
calculating = 4.94575
evaluation = 5.31224
Краткий
и, возможно, несколько парадоксальный итог этого теста может звучать так: при достаточно высоком уровне приоритета (выше 12–13, когда на его выполнение не влияют
процессы обслуживания клавиатуры, мыши и др.) время выполнения в «классическом» последовательном коде и в многопоточном коде (где несколько тысяч потоков!) практически не различаются. Различия не более 8%, причем в обе стороны, что мы склонны считать «статистикой эксперимента». К обсуждению этого якобы противоречащего здравому смыслу феномена мы еще вернемся.
А пока посмотрим на текст примера, что и является нашей главной дачей. Обсуждаемое приложение вполне работоспособно в QNX с большой вероятностью в большинстве других UNIX-систем, но в Linux оно завершится аварийно. Причина этого кроется в операторах
int id = pthread_self - 2;
trtime[id].s = ...
Это дает повод лишний раз обратиться к вопросу «POSIX-совместимости». POSIX описывает, что TID потока присваивается: а) в рамках процесса, которому принадлежит поток; б) начиная со значения 1, соответствующего главному потоку приложения. В Linux, выполняющем и
pthread_create
, и
fork
через единый системный вызов
_clone
сделано небольшое «упрощение», навязанное в том числе и гонкой за повышением производительности: TID присваиваются из единого ряда PID. И сразу же «вылезает» несовместимость, ведущая к аварийному завершению показанного выше приложения. В последних редакциях ядра Linux делаются изменения по приведению механизмов параллельности к общей POSIX-модели.
Этот момент сам по себе достаточно интересен, поэтому остановимся на нем подробнее, для чего создадим простейший программный тест [22] :
#define TCNT 10
void * test(void *in) {
printf("pid %ld, tid %ld\n", getpid, pthread_self);
return NULL;
}
int main(int argc, char **argv, char **envp) {
22
Этот тест и его результаты для Linux подсказаны одним из участников (имя нам неизвестно) обсуждений на http://qnxclub.net.forum.
pthread_t tld[TCNT];
int i, status;
for (i=0; i < TCNT; i++) {
status = pthread_create(&tid[i], NULL, test, NULL);
if (status != 0)
err(EXIT_FAILURE, "pthread_create");
}
return(EXIT_SUCCESS);
}
Результаты выполнения этого теста в нескольких POSIX-совместимых ОС различны и весьма красноречивы: