
Итак, у меня есть программа, написанная на C++.
Он может сказать мне, сколько времени заняло выполнение всех вычислений, и выполняет множество довольно сложных многопоточных вычислений.
Я только что заметил, что если запустить программу на той же машине, то на выполнение всех вычислений уйдет около 20–21 секунды, если запустить ее из TTY, и всего около 0,2 секунды, если запустить ее из терминала GNOME.
Что является причиной? Это буквально тот же самый файл на том же самом компьютере.
решение1
Некоторые теоретические основы
Ну, и то, с чем вы работаете после CTRL++ ALT, F1и GNOME Terminal — это разные реализации одной и той же концепции:эмулирующийтак называемый полноэкранный терминал.
Первая вещь называется виртуальным терминалом (VT) в Linux, или обычно просто "консолью". Она использует специальный "текстовый" видеорежим, который все еще предоставляется аппаратными видеокартами на x86-совместимых платформах (то есть, тех, что относятся к наследию "IBM PC"). Вторая представляет собой приложение с графическим интерфейсом.
Оба предоставляют приложениям, работающим с их помощью, набор возможностей, которые такие приложения ожидают от «терминального устройства» (более подробная информация и дополнительные указания —здесь).
Проблема на данный момент
Хорошо, теперь давайте перейдем к воспринимаемой медлительности.
Я уверен, что суть вашей проблемы в том, что ваша программа делает так называемый "блокирующий" ввод-вывод. То есть, каждый раз, когда вы делаете что-то вроде
std::cout << "Hello, world" << endl;
В вашем коде сначала включается код стандартной библиотеки C++, связанной с вашим приложением, и обрабатывает вывод данных, отправленных в указанноетранслировать.
После определенной обработки (и, как правило, некоторой буферизации) эти данные должны фактически покинуть работающий процесс вашей программы и фактически вывести их на тот носитель, на который ваши программы отправляют свой вывод. В Linux (и других Unix-совместимых системах) для этого требуется вызвать ядро — через выделенныйсистемный вызов(илисистемный вызовдля краткости) названный write()
.
Поэтому библиотека C++ stdlib в конечном итоге выполняет этот write()
системный вызов, а затем ждет его завершения, то есть ждет, пока ядро скажет: «ОК, получатель данных сообщил, что он их получил».
Как вы можете догадаться, получателем данных, которые выводит ваша программа, является терминал (эмулятор), на котором запущена ваша программа — либо виртуальная машина Linux, либо экземпляр терминала GNOME в ваших тестах. (Полная картина сложнее, поскольку ядро не будет отправлять данные прямо в работающий эмулятор терминала, но давайте не будем усложнять описание.)
И поэтому скорость, с которой этот write()
системный вызов завершается, во многом зависит от того, насколько быстро получатель данных обрабатывает их! В вашем случае GNOME Terminal просто делает это намного быстрее.
Я считаю, что разница заключается в том, что драйвер VT добросовестно отображает все отправляемые ему данные, прокручивает их и т. д., в то время как терминал GNOME оптимизирует пакеты входящих данных, отображая только их хвостовую часть (то, что соответствует размеру экрана терминала), а остальное помещает в так называемый «буфер прокрутки», который есть у большинства эмуляторов терминала с графическим интерфейсом.
Что нужно сделать на вынос
Главное, что следует усвоить, это то, что как только ваша программа выполняет какие-либо операции ввода-вывода вместе с вычислениями, и вы измеряете скорость вычислений программы с помощью «настенного» таймера, вы, как правило, можете измерить скорость этого ввода-вывода, а не скорость вычислений.
Обратите внимание, что ввод-вывод — сложная задача: ваш процесс может бытьупрежденный(остановленный, а его ресурсы переданы другому процессу) операционной системой в любой момент, когда она собирается ожидать, когда какой-либо ресурс ввода-вывода станет доступным для записи, например, жесткий диск.
Поэтому верный способ измерить «сырую» производительность вычислений — иметь в своей программе возможность отключить все операции ввода-вывода. Если это невозможно или слишком уродливо для реализации, попробуйте хотя бы направить весь вывод на так называемое «нулевое устройство», /dev/null
запустив свою программу так:
$ ./program >/dev/null
Устройство null просто отбрасывает все переданные ему данные. Так что да, каждый раунд ввода-вывода, выполняемый C++ stdlib, по-прежнему будет попадать в ядро, но по крайней мере у вас будет почти постоянная (и потерянная мгновенная) скорость записи.
Если вам нужны обе мерыиДля получения сгенерированных данных рассмотрите возможность создания так называемого RAM-диска и перенаправления вывода в расположенный там файл.
Еще один момент об измерении: обратите внимание, что даже на, казалось бы, бездействующей системе, работающей на обычной ОС (например, Ubuntu или что-то в этом роде), процессор никогда не спит — всегда есть какие-то задачи, работающие в фоновом режиме. Это означает, что измерение производительности вычислений даже без ввода-вывода или с каким-то «отключенным» вводом-выводом (как объяснялось выше) все равно будет давать разные результаты при каждом запуске.
Чтобы компенсировать это, хороший сравнительный анализ подразумевает выполнение расчетов с одними и теми же входными данными несколько тысяч раз и усреднение результатов по количеству запусков.