if (val != getprio(0))
if (setprio(0, val) == -1)
perror("priority isn't a valid"), exit(EXIT_FAILURE);
break;
case 'n':
if (sscanf(optarg, "%i", &val) != 1)
perror("parse command line failed"), exit(EXIT_FAILURE);
if (val > 0)nsingl *= val;
break;
case 'a':
if (sscanf(optarg, "%i", &val) != 1)
perror("parse command line failed"), exit(EXIT_FAILURE);
if (val > 0) nall = val;
break;
default:
exit(EXIT_FAILURE);
}
}
// ... вот здесь начинается собственно сама программа.
if (nthr > 1)
cout << "Multi-thread evaluation, thread number = " << nthr;
else cout << "Single-thread evaluation";
cout << " , priority level: " << getprio(0) << endl;
__clockperiod clcout;
ClockPeriod(CLOCK_REALTIME, NULL, &clcout, 0);
// интервал диспетчеризации - 4 периода tickslice
// (системного тика):
cout << "rescheduling = \t"
<< clcout.nsec * 4 / 1000000. << endl;
// калибровка времени выполнения в одном потоке
const int NCALIBR = 512;
uint64_t tmin = 0, tmax = 0;
tmin = ClockCycles();
workproc(NCALIBR);
tmax = ClockCycles();
cout << "calculating = \t"
<< cycle2milisec(tmax - tmin) / NCALIBR << endl;
// а теперь контроль времени многих потоков
if (pthread_barrier_init(&bstart, NULL, nthr) != EOK)
perror("barrier init"), exit(EXIT_FAILURE);
if (pthread_barrier_init(&bfinish, NULL, nthr + 1) != EOK)
perror("barrier init"), exit(EXIT_FAILURE);
trtime = new interv[nthr];
int cur = 0, prev = 0;
for (int i = 0; i < nthr; i++) {
// границы участков работы для каждого потока.
cur = (int)floor((double)nall / (double)nthr * (i + 1) + .5);
prev = (int)floor((double)nall / (double)nthr * i + 5);
if (pthread_create(NULL, NULL, threadfunc, (void*)(cur - prev)) != EOK)
perror("thread create"), exit(EXIT_FAILURE);
}
pthread_barrier_wait(&bfinish);
for (int i=0; i < nthr; i++ ) {
tmin = (i == 0) ? trtime[0].s : __min(tmin, trtime[i].s);
tmax = ( i == 0 ) ? trtime[0].f : __max(tmax, trtime[i].f);
}
cout << "evaluation = \t"
<< cycle2milisec(tmax - tmin) / nall << endl;
pthread_barrier_destroy(&bstart);
pthread_barrier_destroy(&bfinish);
delete trtime;
exit(EXIT_SUCCESS);
}
Логика этого приложения крайне проста:
• Есть некоторая продолжительная по времени рабочая функция (workproc), выполняющая массированные вычисления.
• Многократно (это число определяется ключом запуска а) выполняется рабочая функция. Хорошо (то есть корректнее), если время ее единичного выполнения, которое задается ключом n, больше интервала диспетчеризации системы (в системе установлена диспетчеризация по умолчанию - круговая, или карусельная).
• Весь объем этой работы делится поровну (или почти поровну) между несколькими (ключ 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 = ...