- PVSM.RU - https://www.pvsm.ru -
КДПВ
От переводчика:
Большинство моих знакомых для измерения времени в разного вида бенчмарках в С++ используют chrono
или, в особо запущенных случаях, ctime
. Но для бенчмаркинга гораздо полезнее замерять процессорное время. Недавно я наткнулся на статью о кроссплатформенном замере процессорного времени и решил поделиться ею тут, возможно несколько увеличив качество местных бенчмарков.
P.S. Когда в статье написано "сегодня" или "сейчас", имеется ввиду "на момент выхода статьи", то есть, если я не ошибаюсь, март 2012. Ни я, ни автор не гарантируем, что это до сих пор так.
P.P.S. На момент публикации оригинал недоступен, но хранится в кэше Яндекса [1]
Функции API, позволяющие получить процессорное время, использованное процессом, отличаются в разных операционных системах: Windows, Linux, OSX, BSD, Solaris, а также прочих UNIX-подобных ОС. Эта статья предоставляет кросс-платформенную функцию, получающую процессорное время процесса и объясняет, какие функции поддерживает каждая ОС.
Процессорное время увеличивается, когда процесс работает и потребляет циклы CPU. Во время операций ввода-вывода, блокировок потоков и других операций, которые приостанавливают работу процессора, процессорное время не увеличивается пока процесс снова не начнет использовать CPU.
Разные инструменты, такие как ps
в POSIX, Activity Monitor в OSX и Task Manager в Windows показывают процессорное время, используемое процессами, но часто бывает полезным отслеживать его прямо из самого процесса. Это особенно полезно во время бенчмаркинга алгоритмов или маленькой части сложной программы. Несмотря на то, что все ОС предоставляют API для получения процессорного времени, в каждой из них есть свои тонкости.
Функция getCPUTime( )
, представленная ниже, работает на большинстве ОС (просто скопируйте код или скачайте файл getCPUTime.c [2]). Там, где это нужно, слинкуйтесь с librt, чтобы получить POSIX-таймеры (например, AIX, BSD, Cygwin, HP-UX, Linux и Solaris, но не OSX). В противном случае, достаточно стандартных библиотек.
Далее мы подробно обсудим все функции, тонкости и причины, по которым в коде столько #ifdef
'ов.
/*
* Author: David Robert Nadeau
* Site: http://NadeauSoftware.com/
* License: Creative Commons Attribution 3.0 Unported License
* http://creativecommons.org/licenses/by/3.0/deed.en_US
*/
#if defined(_WIN32)
#include <Windows.h>
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
#include <unistd.h>
#include <sys/resource.h>
#include <sys/times.h>
#include <time.h>
#else
#error "Unable to define getCPUTime( ) for an unknown OS."
#endif
/**
* Returns the amount of CPU time used by the current process,
* in seconds, or -1.0 if an error occurred.
*/
double getCPUTime( )
{
#if defined(_WIN32)
/* Windows -------------------------------------------------- */
FILETIME createTime;
FILETIME exitTime;
FILETIME kernelTime;
FILETIME userTime;
if ( GetProcessTimes( GetCurrentProcess( ),
&createTime, &exitTime, &kernelTime, &userTime ) != -1 )
{
SYSTEMTIME userSystemTime;
if ( FileTimeToSystemTime( &userTime, &userSystemTime ) != -1 )
return (double)userSystemTime.wHour * 3600.0 +
(double)userSystemTime.wMinute * 60.0 +
(double)userSystemTime.wSecond +
(double)userSystemTime.wMilliseconds / 1000.0;
}
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
/* Prefer high-res POSIX timers, when available. */
{
clockid_t id;
struct timespec ts;
#if _POSIX_CPUTIME > 0
/* Clock ids vary by OS. Query the id, if possible. */
if ( clock_getcpuclockid( 0, &id ) == -1 )
#endif
#if defined(CLOCK_PROCESS_CPUTIME_ID)
/* Use known clock id for AIX, Linux, or Solaris. */
id = CLOCK_PROCESS_CPUTIME_ID;
#elif defined(CLOCK_VIRTUAL)
/* Use known clock id for BSD or HP-UX. */
id = CLOCK_VIRTUAL;
#else
id = (clockid_t)-1;
#endif
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
return (double)ts.tv_sec +
(double)ts.tv_nsec / 1000000000.0;
}
#endif
#if defined(RUSAGE_SELF)
{
struct rusage rusage;
if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
return (double)rusage.ru_utime.tv_sec +
(double)rusage.ru_utime.tv_usec / 1000000.0;
}
#endif
#if defined(_SC_CLK_TCK)
{
const double ticks = (double)sysconf( _SC_CLK_TCK );
struct tms tms;
if ( times( &tms ) != (clock_t)-1 )
return (double)tms.tms_utime / ticks;
}
#endif
#if defined(CLOCKS_PER_SEC)
{
clock_t cl = clock( );
if ( cl != (clock_t)-1 )
return (double)cl / (double)CLOCKS_PER_SEC;
}
#endif
#endif
return -1; /* Failed. */
}
Чтобы замерить процессорное время алгоритма, вызовите getCPUTime( )
до и после запуска алгоритма, и выведите разницу. Не стоит предполагать, что значение, возвращенное при единичном вызове функции, несет какой-то смысл.
double startTime, endTime;
startTime = getCPUTime( );
...
endTime = getCPUTime( );
fprintf( stderr, "CPU time used = %lfn", (endTime - startTime) );
Каждая ОС предоставляет один или несколько способов получить процессорное время. Однако некоторые способы точнее остальных.
OS | clock | clock_gettime | GetProcessTimes | getrusage | times |
---|---|---|---|---|---|
AIX | yes | yes | yes | yes | |
BSD | yes | yes | yes | yes | |
HP-UX | yes | yes | yes | yes | |
Linux | yes | yes | yes | yes | |
OSX | yes | yes | yes | ||
Solaris | yes | yes | yes | yes | |
Windows | yes |
Каждый из этих способов подробно освещен ниже.
На Windows и Cygwin [3] (UNIX-подобная среда и интерфейс командной строки для Windows), функция GetProcessTimes( ) [4] заполняет структуру FILETIME [5] процессорным временем, использованным процессом, а функция FileTimeToSystemTime( ) [6] конвертирует структуру FILETIME в структуру SYSTEMTIME [7], содержащую пригодное для использования значение времени.
typedef struct _SYSTEMTIME
{
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
Доступность GetProcessTimes( ): Cygwin, Windows XP и более поздние версии.
Получение процессорного времени:
#include <Windows.h>
...
FILETIME createTime;
FILETIME exitTime;
FILETIME kernelTime;
FILETIME userTime;
if ( GetProcessTimes( GetCurrentProcess( ),
&createTime, &exitTime, &kernelTime, &userTime ) != -1 )
{
SYSTEMTIME userSystemTime;
if ( FileTimeToSystemTime( &userTime, &userSystemTime ) != -1 )
return (double)userSystemTime.wHour * 3600.0 +
(double)userSystemTime.wMinute * 60.0 +
(double)userSystemTime.wSecond +
(double)userSystemTime.wMilliseconds / 1000.0;
}
На большинстве POSIX-совместимых ОС, clock_gettime( )
(смотри мануалы к AIX [8], BSD [9], HP-UX [10], Linux [11] и Solaris [12]) предоставляет самое точное значение процессорного времени. Первый аргумент функции выбирает "clock id", а второй это структура timespec
, заполняемая использованным процессорным временем в секундах и наносекундах. Для большинства ОС, программа должна быть слинкована с librt.
Однако, есть несколько тонкостей, затрудняющих использование этой функции в кросс-платформенном коде:
_POSIX_TIMERS
определен в <unistd.h>
значением больше 0. На сегодняшний день, AIX, BSD, HP-UX, Linux и Solaris поддерживают эту функцию, но OSX не поддерживает.timespec
, заполняемая функцией clock_gettime( )
может хранить время в наносекундах, но точность часов отличается в разных ОС и на разных системах. Функция clock_getres( ) [11] возвращает точность часов, если она вам нужна. Эта функция, опять-таки, является опциональной частью стандарта POSIX, доступной только если _POSIX_TIMERS
больше нуля. На данный момент, AIX, BSD, HP-UX, Linux и Solaris предоставляют эту функцию, но в Solaris она не работает.CLOCK_PROCESS_CPUTIME_ID
, чтобы получить процессорное время процесса. Тем не менее, сегодня BSD и HP-UX не имеют этого id, и взамен определяют собственный id CLOCK_VIRTUAL
для процессорного времени. Чтобы запутать все ещё больше, Solaris определяет оба этих, но использует CLOCK_VIRTUAL
для процессорного времени потока, а не процесса.ОС | Какой id использовать |
---|---|
AIX | CLOCK_PROCESS_CPUTIME_ID |
BSD | CLOCK_VIRTUAL |
HP-UX | CLOCK_VIRTUAL |
Linux | CLOCK_PROCESS_CPUTIME_ID |
Solaris | CLOCK_PROCESS_CPUTIME_ID |
_POSIX_CPUTIME
больше 0. На сегодняшний день, только AIX и Linux предоставляют эту функцию, но линуксовские include-файлы не определяют _POSIX_CPUTIME
и функция возвращает ненадёжные и несовместимые с POSIX результаты.clock_gettime( )
может быть реализована с помощью регистра времени процессора. На многопроцессорных системах, у отдельных процессоров может быть несколько разное восприятие времени, из-за чего функция может возвращать неверные значения, если процесс передавался от процессора процессору. На Linux, и только на Linux, это может быть обнаружено, если clock_getcpuclockid( )
возвращает не-POSIX ошибку и устанавливает errno
в ENOENT
. Однако, как замечено выше, на Linux clock_getcpuclockid( )
ненадежен.На практике из-за всех этих тонкостей, использование clock_gettime( )
требует много проверок с помощью #ifdef
и возможность переключиться на другую функцию, если она не срабатывает.
Доступность clock_gettime( ): AIX, BSD, Cygwin, HP-UX, Linux и Solaris. Но clock id на BSD и HP-UX нестандартные.
Доступность clock_getres( ): AIX, BSD, Cygwin, HP-UX и Linux, но не работает Solaris.
Доступность clock_getcpuclockid( ): AIX и Cygwin, не недостоверна на Linux.
Получение процессорного времени:
#include <unistd.h>
#include <time.h>
...
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
clockid_t id;
struct timespec ts;
#if _POSIX_CPUTIME > 0
/* Clock ids vary by OS. Query the id, if possible. */
if ( clock_getcpuclockid( 0, &id ) == -1 )
#endif
#if defined(CLOCK_PROCESS_CPUTIME_ID)
/* Use known clock id for AIX, Linux, or Solaris. */
id = CLOCK_PROCESS_CPUTIME_ID;
#elif defined(CLOCK_VIRTUAL)
/* Use known clock id for BSD or HP-UX. */
id = CLOCK_VIRTUAL;
#else
id = (clockid_t)-1;
#endif
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
return (double)ts.tv_sec +
(double)ts.tv_nsec / 1000000000.0;
#endif
На всех UNIX-подобных ОС, функция getrusage( ) [15] это самый надежный способ получить процессорное время, использованное текущим процессом. Функция заполняет структуру rusage временем в секундах и микросекундах. Поле ru_utime
содержит время проведенное в user mode, а поле ru_stime
— в system mode от имени процесса.
Внимание: Некоторые ОС, до широкого распространения поддержки 64-бит, определяли функцию getrusage( )
, возвращающую 32-битное значение, и функцию getrusage64( )
, возвращающую 64-битное значение. Сегодня, getrusage( )
возвращает 64-битное значение, аgetrusage64( )
устарело.
Доступность getrusage( ): AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris.
Получение процессорного времени:
#include <sys/resource.h>
#include <sys/times.h>
...
struct rusage rusage;
if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
return (double)rusage.ru_utime.tv_sec +
(double)rusage.ru_utime.tv_usec / 1000000.0;
На всех UNIX-подобных ОС, устаревшая функция times( ) [16] заполняет структуру tms
с процессорным временем в тиках, а функция sysconf( ) [17] возвращает количество тиков в секунду. Поле tms_utime
содержит время, проведенное в user mode, а поле tms_stime
— в system mode от имени процесса.
Внимание: Более старый аргумент функции sysconf( )
CLK_TCK
устарел и может не поддерживаться в некоторых ОС. Если он доступен, функция sysconf( )
обычно не работает при его использовании. Используйте _SC_CLK_TCK
вместо него.
Доступность times( ): AIX, BSD, Cygwin, HP-UX, Linux, OSX и Solaris.
Получение процессорного времени:
#include <unistd.h>
#include <sys/times.h>
...
const double ticks = (double)sysconf( _SC_CLK_TCK );
struct tms tms;
if ( times( &tms ) != (clock_t)-1 )
return (double)tms.tms_utime / ticks;
На всех UNIX-подобных ОС, очень старая функция clock( ) [18] возвращает процессорное время процесса в тиках, а макрос CLOCKS_PER_SEC
количество тиков в секунду.
Заметка: Возвращенное процессорное время включает в себя время проведенное в user mode И в system mode от имени процесса.
Внимание: Хотя изначально CLOCKS_PER_SEC
должен был возвращать значение, зависящее от процессора, стандарты C ISO C89 и C99, Single UNIX Specification и стандарт POSIX требуют, чтобы CLOCKS_PER_SEC
имел фиксированное значение 1,000,000, что ограничивает точность функции микросекундами. Большинство ОС соответствует этим стандартам, но FreeBSD, Cygwin и старые версии OSX используют нестандартные значения.
Внимание: На AIX и Solaris, функция clock( )
включает процессорное время текущего процесса И и любого завершенного дочернего процесса для которого родитель выполнил одну из функций wait( )
, system( )
или pclose( )
.
Внимание: В Windows, функция clock( ) [19] поддерживается, но возвращает не процессорное, а реальное время.
Доступность clock( ): AIX, BSD, Cygwin, HP-UX, Linux, OSX и Solaris.
Получение процессорного времени:
#include <time.h>
...
clock_t cl = clock( );
if ( cl != (clock_t)-1 )
return (double)cl / (double)CLOCKS_PER_SEC;
Существуют и другие ОС-специфичные способы получить процессорное время. На Linux, Solarisи некоторых BSD, можно парсить /proc/[pid]/stat [20], чтобы получить статистику процесса. На OSX, приватная функция API proc_pidtaskinfo( )
в libproc
возвращает информацию о процессе. Также существуют открытые библиотеки, такие как libproc, procps [21] и Sigar [22].
На UNIX существует несколько утилит позволяющих отобразить процессорное время процесса, включая ps [23], top [24], mpstat [25] и другие. Можно также использовать утилиту time [26], чтобы отобразить время, потраченное на команду.
На Windows, можно использовать диспетчер задач [27], чтобы мониторить использование CPU.
На OSX, можно использовать Activity Monitor [28], чтобы мониторить использование CPU. Утилита для профайлинга Instruments [29] поставляемая в комплекте с Xcode может мониторить использование CPU, а также много других вещей.
#ifdef
для ОС-специфичного кода. Часть из этих методов использовано в этой статье, чтобы определить Windows, OSX и варианты UNIX.Автор: Randl
Источник [37]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/optimizatsiya-koda/118953
Ссылки в тексте:
[1] кэше Яндекса: https://hghltd.yandex.net/yandbtm?fmode=inject&url=http%3A%2F%2Fnadeausoftware.com%2Farticles%2F2012%2F03%2Fc_c_tip_how_measure_cpu_time_benchmarking&tld=ru&lang=en&la=1458494720&tm=1461429362&text=http%3A%2F%2Fnadeausoftware.com%2Farticles%2F2012%2F03%2Fc_c_tip_how_measure_cpu_time_benchmarking%23clockgettme&l10n=ru&mime=html&sign=acfadd1eee05fdfb3a0c525c0ae469db&keyno=0
[2] getCPUTime.c: https://gist.github.com/Randl/45bcca59720f661fa033a67d5f44bff0
[3] Cygwin: http://cygwin.com/index.html
[4] GetProcessTimes( ): https://msdn.microsoft.com/en-us/library/windows/desktop/ms683223(v=vs.150).aspx
[5] FILETIME: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.150).aspx
[6] FileTimeToSystemTime( ): https://msdn.microsoft.com/en-us/library/windows/desktop/ms724280(v=vs.150).aspx
[7] SYSTEMTIME: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724950(v=vs.150).aspx
[8] AIX: https://www.ibm.com/support/knowledgecenter/ssw_aix_71/com.ibm.aix.basetrf1/clock_getres.htm
[9] BSD: https://www.freebsd.org/cgi/man.cgi?query=clock_gettime
[10] HP-UX: http://nixdoc.net/man-pages/HP-UX/man2/clock_getres.2.html
[11] Linux: http://man7.org/linux/man-pages/man2/clock_gettime.2.html
[12] Solaris: https://docs.oracle.com/cd/E23824_01/html/821-1465/clock-gettime-3c.html#scrolltoc
[13] стандарт POSIX: http://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_gettime.html
[14] clock_getcpuclockid( ): http://man7.org/linux/man-pages/man3/clock_getcpuclockid.3.html
[15] getrusage( ): http://man7.org/linux/man-pages/man2/getrusage.2.html
[16] times( ): http://man7.org/linux/man-pages/man2/times.2.html
[17] sysconf( ): http://linux.die.net/man/3/sysconf
[18] clock( ): http://man7.org/linux/man-pages/man3/clock.3.html
[19] clock( ): https://msdn.microsoft.com/en-us/library/4e2ess30(v=vs.150)
[20] /proc/[pid]/stat: http://man7.org/linux/man-pages/man5/proc.5.html
[21] procps: http://procps.sourceforge.net/
[22] Sigar: https://support.hyperic.com/display/SIGAR/Home
[23] ps: http://linux.die.net/man/1/ps
[24] top: http://linux.die.net/man/1/top
[25] mpstat: https://en.wikipedia.org/wiki/Mpstat
[26] time: http://man7.org/linux/man-pages/man1/time.1.html
[27] диспетчер задач: https://ru.wikipedia.org/wiki/%D0%94%D0%B8%D1%81%D0%BF%D0%B5%D1%82%D1%87%D0%B5%D1%80_%D0%B7%D0%B0%D0%B4%D0%B0%D1%87_Windows
[28] Activity Monitor: https://en.wikipedia.org/wiki/List_of_OS_X_components#Activity_Monitor
[29] Instruments: https://developer.apple.com/library/tvos/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/
[30] Creative Commons Attribution 3.0 Unported License: https://creativecommons.org/licenses/by/3.0/deed.en_US
[31] C/C++ tip: How to measure elapsed real time for benchmarking: http://nadeausoftware.com/articles/2012/04/c_c_tip_how_measure_elapsed_real_time_benchmarking
[32] C/C++ tip: How to use compiler predefined macros to detect the operating system: http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system
[33] Процессорное время: https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81%D0%BE%D1%80%D0%BD%D0%BE%D0%B5_%D0%B2%D1%80%D0%B5%D0%BC%D1%8F
[34] CPU Time Inquiry: https://www.gnu.org/software/libc/manual/html_node/CPU-Time.html
[35] Determine CPU usage of current process (C++ and C#): http://www.philosophicalgeek.com/2009/01/03/determine-cpu-usage-of-current-process-c-and-c/
[36] Posix Options: http://man7.org/linux/man-pages/man7/posixoptions.7.html
[37] Источник: https://habrahabr.ru/post/282301/
Нажмите здесь для печати.