Я написал что-то, чтобы измерить, сколько времени занимает запуск моего кода, и распечатать его. То, как я это понял, теперь поддерживает вложение этих измерений.
Дело в том, что в процессе получения временного интервала, преобразования его в число, получения формата времени, а затем преобразования всего в строку и последующей ее распечатки требуется некоторое время (2-3 миллисекунды), особенно ввод / вывод кажется дорогим. Я хочу, чтобы часы «пропустили» этот процесс в некотором смысле, так как измеряемый материал будет в микросекундах. (и я думаю, что это будет хорошая функция, если у меня есть другие вещи, которые я хочу пропустить)
std::chrono::high_resolution_clock clock;
std::chrono::time_point<std::chrono::steady_clock> first, second;
first = clock.now();
std::chrono::time_point<std::chrono::high_resolution_clock> paused_tp = clock.now();
std::cout << "Printing things \n";
clock.setTime(paused_tp); // Something like this is what I want
second = clock.now();
Идея состоит в том, чтобы убедиться, что first
а также second
иметь минимальные различия, идеально идентичные.
Из того, что я вижу, high_resolution_clock
класс (и все остальные chrono
часы), держи их time_point
частный, и вы можете получить к нему доступ только с clock.now()
Я знаю, что могут быть библиотеки для бенчмаркинга, которые делают это, но я хотел бы выяснить, как сделать это самому (хотя бы ради знания, как это сделать). Любая информация о том, как это делают другие библиотеки или понимание того, как работает хронограф, также будет оценена. Я мог бы неправильно понять, как chrono
внутренне отслеживает.
(Является std::chrono::high_resolution_clock
даже достаточно точно для чего-то подобного?)
(Хотя я здесь, любые ресурсы по повышению эффективности программ на C ++ были бы хорошими)
Редактировать: Я на самом деле печатаю после фрагмента кода, который пытаюсь рассчитать, проблема возникает, например, в том случае, когда я хочу синхронизировать всю программу, а также отдельные функции. Тогда печать времени функции приведет к задержке общего времени программы.
Редактировать 2: Я подумал, что мне нужно больше примеров того, что я делаю.
У меня есть класс, который обрабатывает все, скажем, он называется tracker
Он заботится обо всех этих глупостях.
tracker loop = TRACK(
for(int i = 0; i != 100; ++i){
tracker b = TRACK(function_call());
b.display();
}
)
loop.display();
Макрос не является обязательным, он просто быстрый, что делает его менее загроможденным и позволяет отображать имя вызываемой функции.
Явно макрос расширяется до
tracker loop = "for(int i = 0; i != 100; ++i){ tracker b = TRACK(function_call()); b.display(); }"loop.start()
for(int i = 0; i != 100; ++i){
tracker b = "function_call()"b.start();
function_call();
b.end();
b.display();
}
loop.end();
loop.display();
В большинстве случаев печать не является проблемой, она только отслеживает, что между start()
а также end()
, но здесь b.display()
в конечном итоге вмешивается в tracker loop
,
Моя цель с этим состояла в том, чтобы трекер был как можно более ненавязчивым, поэтому я бы хотел, чтобы большинство / все это обрабатывалось в классе трекера. Но потом я сталкиваюсь с проблемой b.display()
будучи методом другого экземпляра, чем tracker loop
, Я попробовал несколько вещей с static
ключевое слово, но столкнулся с несколькими проблемами с этим (все еще пробуя немного). Возможно, я застрял здесь в тупике, но еще многое предстоит попробовать.
Просто выделите два интервала отдельно и добавьте их, т.е. сохранить 4 всего времени. Для вложенных интервалов вы можете просто сохранить метки времени в массиве и отсортировать все в конце. (Или внутри внешнего цикла до того, как метки времени будут перезаписаны). Хранение в массиве довольно дешево.
Или лучше: отложите печать на потом.
Если временной интервал составляет всего миллисекунды, просто сохраните то, что вы собирались распечатать, и сделайте это вне временного интервала.
Если у вас есть вложенные временные интервалы, по крайней мере потяните печать из самых внутренних интервалов, чтобы минимизировать количество остановок / перезапусков, которые вы должны сделать.
Если вы обрабатываете код вручную везде, возможно, посмотрите на инструменты профилирования как флеймограф, особенно если ваши временные интервалы нарушают границы функций. linux perf: как интерпретировать и находить горячие точки.
Мало того, что ввод / вывод требует времени сам по себе, он замедлит выполнение последующего кода на несколько сотен или тысяч циклов. Выполнение системного вызова затрагивает много кода, поэтому, когда вы вернетесь в пространство пользователя, вероятно, вы получите ошибки кэша команд и данных. Системные вызовы, которые изменяют ваши таблицы страниц, также приводят к пропускам TLB.
(См. Раздел «(Реальные) затраты на системные вызовы» в документе FlexSC (Soares, Stumm), приурочен к i7 Nehalem под управлением Linux. (Первый поколения i7 из ~ 2008/9). В статье предлагается пакетный механизм системных вызовов для высокопроизводительных веб-серверов и тому подобное, но их базовые результаты для простого Linux интересны и актуальны вне этого.)
На современном процессоре Intel с включенным смягчением Meltdown вы, как правило, будете получать пропуски TLB. Если в недавнем выпуске x86 включено смягчение спектра, история предсказаний ветвлений, вероятно, будет уничтожена, в зависимости от стратегии смягчения. (Intel добавила в ядро способ запрашивать, чтобы ветви с более высокими привилегиями после этой точки не влияли на историю предсказаний для ветвей с более низкими привилегиями. Я думаю, что на современных процессорах это просто сбрасывает кэш предсказания ветвлений.)
Вы можете избежать издержек системного вызова, позволяя iostream
буфер для вас. Это все еще значительная работа форматирование и копирование данных вокруг, но много дешевле, чем писать в терминал. Перенаправление вашей программы stdout
в файл сделаю cout
полный буферизованный по умолчанию, а не линейный. т.е. запустить его так:
./my_program > time_log.txt
Окончательный результат будет соответствовать тому, что вы получили бы на терминале, но (до тех пор, пока вы не делаете ничего глупого, как использование std::endl
вызвать флеш), он просто будет забуферен. Размер буфера по умолчанию, вероятно, что-то вроде 4kiB. использование strace ./my_program
или аналогичный инструмент для отслеживания системных вызовов, и убедитесь, что вы получаете один большой write()
в конце вместо множества маленьких write()
s.
Было бы неплохо избежать буферизованного ввода-вывода внутри (внешних) синхронизированных областей, но очень важно избегать системных вызовов там, где ваш «реальный» (неинструментированный) код не будет иметь их, если вы рассчитываете время до наносекунд. И это правда, даже до временные интервалы, а не только внутри.
cout << foo
если это не делает системный вызов, не является «особенным» с точки зрения замедления последующего кода.
Чтобы избежать накладных расходов, печать с замедленной печатью может быть выполнена другим потоком. Основной поток сохраняет время начала и окончания в общих глобальных переменных и уведомляет переменная условия печатная нить ожидает
#include<iostream>
#include<thread>
#include<chrono>
#include<mutex>
#include<condition_variable>
#include<atomic>
std::condition_variable cv;
std::mutex mu;
std::atomic<bool> running {true};
std::atomic<bool> printnow {false};
// shared but non-atomic: protected by the mutex and condition variable.
// possible bug: the main thread can write `now` before print_thread wakes up and reads it
std::chrono::high_resolution_clock::time_point start;
std::chrono::high_resolution_clock::time_point now;void print_thread() {
std::thread([]() {
while (running) {
std::unique_lock<std::mutex> lock(mu);
cv.wait(lock, []() { return !running || printnow; });
if (!running) return;
std::chrono::milliseconds lapse_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
printnow = false;
std::cout << " lapse time " << lapse_ms.count() << " ms\n";
}
}).detach();
}
void print_lapse(std::chrono::high_resolution_clock::time_point start1, std::chrono::high_resolution_clock::time_point now1) {
start = start1;
now = now1;
printnow = true;
cv.notify_one();
}
int main()
{
//launch thread
print_thread();
// laspe1
std::chrono::high_resolution_clock::time_point start1 = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::milliseconds(200));
std::chrono::high_resolution_clock::time_point now1 = std::chrono::high_resolution_clock::now();
print_lapse(start1,now1);
// laspe2
start1 = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::milliseconds(500));
now1 = std::chrono::high_resolution_clock::now();
print_lapse(start1, now1);
//winding up
std::this_thread::sleep_for(std::chrono::milliseconds(300));
running = false;
cv.notify_one();
}