Цилюрик Олег Владимирович - QNX/UNIX: Анатомия параллелизма стр 21.

Шрифт
Фон

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 = ...

Ваша оценка очень важна

0
Шрифт
Фон

Помогите Вашим друзьям узнать о библиотеке