Соображения производительности
Ранее мы производили оценку затрат производительности процессора на переключение между контекстами для процессов и для потоков (тестовые задачи, которые мы по их структуре именовали как «симметричные»). Проделаем теперь то же самое, но синхронизацию процессов выполним посылкой и приемом сигнала (переключение контекстов будет происходить именно на операторе
pause()
#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <iostream.h>
#include <unistd.h>
#include <sched.h>
#include <sys/neutrino.h>
// "пустые" обработчики сигналов
static void nhand(int signo) {}
static void qhand(int signo, siginfo_t* info, void* context) {}
int main(int argc, char *argv[]) {
unsigned long N = 1000;
bool que = false;
int opt, val;
while ((opt = getopt(argc, argv, "n:q")) != -1) {
switch(opt) {
case 'n':
if (sscanf(optarg, "%i", &val) != 1)
cout << "parse command line error" << endl, exit(EXIT_FAILURE);
if (val > 0) N = val;
break;
// ключ q определяет схему обработки сигнала
case 'q':
que = true;
break;
default:
exit(EXIT_FAILURE);
}
}
// установка сигнальных обработчиков
sigset_t sig;
sigemptyset(&sig);
sigaddset(&sig, SIGUSR1);
sigprocmask(SIG_UNBLOCK, &sig, NULL);
struct sigaction act;
act.sa_mask = sig;
act.sa_sigaction = qhand;
act.sa_handler = nhand;
act.sa_flags = que ? SA_SIGINFO : 0;
if (sigaction(SIGUSR1, &act, NULL) < 0)
cout << "set signal handler" << endl, exit(EXIT_FAILURE);
pid_t pid = fork();
if (pid == -1)
cout << "fork error" << endl, exit(EXIT_FAILURE);
// кому отправлять сигнал?
pid_t did = (pid == 0 ? getppid() : pid);
unsigned long i = 0;
uint64_t t = ClockCycles();
while (true) {
kill(did, SIGUSR1);
if (++i == N) break;
pause();
}
t = ClockCycles() - t;
cout << getpid() << " -> " << did << "\t: cycles - " << t <<
"; on signal - " << (t / N) / 2 << endl;
exit(EXIT_SUCCESS);
}
Этим приложением мы можем тестировать и традиционную схему обработки сигналов (модель надежных сигналов), и схему обработки с очередью поступления сигналов (модель сигналов реального времени), когда при старте программы указан ключ
-q
# nice -n-19 p6s -n1000
2904115 -> 2912308 : cycles - 5792027; on signal - 2896
2912308 -> 2904115 : cycles - 5828952; on signal — 2914
# nice -n-19 p6s -n10000
2920499 -> 2928692 : cycles - 57522753, on signal - 2876
2928692 -> 2920499 : cycles - 57530378; on signal
- 2876
# nice -n-19 p6s -n100000
2936883 -> 2945076 : cycles - 573730469; on signal - 2868
2945076 -> 2936883 : cycles - 573738122; on signal - 2868
# nice -n-19 p6s -n1000000
2953267 -> 2961460 : cycles - 5747418203, on signal - 2873
2961460 -> 2953267 : cycles - 5747425310; on signal - 2873
Вспомним, что при изучении тестов простого переключения процессов (см. в главе 2) мы получали цифру порядка 600 процессорных циклов на переключение. Сейчас у нас затраты заметно больше: порядка 2850 циклов, из которых «лишние» 2250 — это не что иное, как затраты на посылку и прием сигнала, возбуждение функции обработчика и ее завершение (разделить их по компонентам мы не можем). Это и может служить ориентировочной оценкой трудоемкости обмена сигналами.
Проделаем то же самое, но уже при обработке сигналов в порядке очереди их поступления:
# nice -n-19 p6s -n1000 -q
2838579 -> 2846772 : cycles - 5772106; on signal - 2886
2846772 -> 2838579 : cycles - 5782138; on signal - 2891