Решил обновить свою старую крохотную библиотечку для тайминга кода. Измерение коротких временнЫх интервалов в ней выполнялось с помощью: инструкции RDTSC при компиляции MSC для x86 и gettimeofday при сборке GCC под Linux. IMHO, на сегодняшний день это не самые правильные методы, и захотелось объективно выбрать что-то более точное и лёгкое. Для этого были измерены несколько параметров наиболее популярных функций измерения времени. Результатами этих измерений и делюсь с вами (вдруг кому тоже интересно).
Для удобства сравнения в таблице ниже приведены результаты тестирования на одной машине с процессором i7-6700K@4.00GHz, но под двумя ОС: Win10 22H2 и Ubuntu 22.04. Подобные тесты проводились и на других машинах (всего 8 штук включая два Raspberry Pi: 1B+ и 4B), но расширять таблицу не стал, результаты аналогичные. Все числа в статье замерены на указанной выше тестовой машине!
В таблице ниже все значения указаны в микросекундах. Discreteness - минимальный прирост показаний функции (очень грубо говоря это точность измерения), Duration - длительность вызова самой функции (overhead), One tick - значение единицы измерения, Type - тип функции (мировое время, время потока или процесса).
Краткие (если получится) замечания по наиболее популярным функциям измерения времени:
clock() [С89] – Описываю исключительно из уважения к этой древнейшей функции, которая появилась ещё в первой редакции ANSI C и предназначалась как раз для точного измерения интервалов времени. По стандарту функция должна возвращать «processor time used by the program so far» но по историческим причинам её реализация у Microsoft не соответствует стандарту и возвращает «the elapsed time since the CRT initialization at the start of the process». Это различие не имело значения в одно-процессных ОС (например, в MS DOS) но принципиально для современных многозадачных ОС. Так что для портируемых приложений эта функция не годится. Кроме того, «правильная» реализация функции в Linux ну оооочень медленная (длительность вызова 960ns(!!!) против 36ns на Windows). Вообще все функции, возвращающие время, связанное с процессом или потоком, очень неспешны, видимо их реализация требует перехода в режим ядра, а это, как известно, операция дорогая. Дискретность функции сегодня тоже не удовлетворительна (1ms в Windows и 1µs в POSIX).
timespec_get() [C11] – Позволяет получать системное время в наносекундах. Но не следует путать единицы измерения и точность – в Windows эта функция имеет дискретность 100ns а в Linux зависит от железа (22ns на тестовой машине и 1,1µs(!!!) на Raspberry Pi 1B+). То, что функция возвращает системное время, делает её не монотонной – система может корректировать время (даже назад).
std::chrono [C++11] - функции now() классов high_resolution_clock и steady_clock достаточно лёгкие и позволяют получать время в наносекундах (см. комментарий к timespec_get()). В стандарте оговаривается, что high_resolution_clock предоставляет наиточнейшее измерение времени, но есть нюанс, эти часы не обязаны быть steady. В Linux high_resolution_clock является синонимом для system_clock а значит зависит от коррекции времени, как и timespec_get, короче говоря, в Linux время измеренное high_resolution_clock может уменьшаться! Зачем так сделано не знаю. В Windows этот класс является синонимом steady_clock и потому является монотонным. На исследованных платформах дискретность и длительность измерения времени с помощью всех классов std::chrono оказались одинаковы и соответствуют timespec_get(). В большинстве случаев можно прямо использовать steady_clock и не связываться с high_resolution_clock.
gettimeofday() [POSIX] – позволяет получать время в микросекундах. На сегодня считается устаревшей - «POSIX.1-2008 marks gettimeofday() as obsolete, recommending the use of clock_gettime() instead». Упоминаю её лишь потому, что она часто используется в примерах из интернета.
clock_gettime(CLOCK_MONOTONIC_RAW) [Linux] – видимо лучший вариант для тайминга в LinuxAPI. Точность и время выполнения около 22ns.
QueryPerformanceCounter() [WinAPI] – хороший и очень популярный вариант из WinAPI. Функция монотонна, дискретность – 100ns (хотелось бы поменьше), время выполнения - 13ns(!).
RDTSC/RDTSCP - для измерения интервалов времени на x86 процессорах часто рекомендуют эти инструкции. На тестовой машине они выполняются за 6,4/9,2ns (при частоте процессора 4,0GHz это примерно 26/37 тактов). TSC приписывают несколько проблем: а) счётчик может обновляться с непостоянной скоростью; б) на разных ядрах они могут быть не синхронизированы; в) процессор может переупорядочить инструкции, возвращающие значение счётчика.
cntvct_el0 - Counter-timer Virtual Count регистр ARM процессора. В таблицу не попал, но был испытан на RPi 4B. Доступен в пользовательском режиме и показывает дискретность равную 18,5ns при длительности вызова 5.0ns (для процессора с частотой 1.8GHz это примерно 33 и 9 тактов). Пример кода для чтения этого регистра:
inline uint64_t vi_get_cntvct()
{ uint64_t result;
__asm__ __volatile__("mrs %0, cntvct_el0" : "=r"(result));
return result;
}
Чуть подробнее про упомянутые выше проблемы TSC на x86 процессоре
Первая проблема - уже не проблема: «For Pentium 4 processors, Intel Xeon processors <…>; for Intel Core Solo and Intel Core Duo processors <...>; for the Intel Xeon processor 5100 series and Intel Core 2 Duo processors <...>; for Intel Core 2 and Intel Xeon processors <…>; for Intel Atom processors <…>: the time-stamp counter increments at a constant rate” (Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4. Vol. 3B. P.18-42)
Вторую проблему рекомендуют уменьшить двумя способами:
Использовать сочетание инструкций RDTSCP и LFANCE: «The RDTSCP instruction is not a serializing instruction, but it does wait until all previous instructions have executed <…> If software requires RDTSCP to be executed prior to execution of any subsequent instruction (including any memory accesses), it can execute LFENCE immediately after RDTSCP» (Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4. Vol. 2B. P.4-553). Если я правильно понял, то рекомендуется нечто подобное коду ниже (при включении соответствующих заголовочных файлов эти intrinsic функции компилируются и в GCC, и в MSC):
inline uint64_t vi_get_tsc()
{ uint32_t aux; // Будет удалён оптимизатором
const uint64_t result = __rdtscp(&aux);
_mm_lfence();
return result;
}Другой популярный в интернете совет сериализации инструкций в ассемблере – использовать CPUID:
inline uint64_t vi_get_tsc()
{ int32_t cpuInfo[4]; // Будет удалён оптимизатором
uint32_t aux;
const uint64_t result = __rdtscp(&aux);
__cpuid(cpuInfo, 0);
return result;
}
Но! При использовании CPUID под Windows я столкнулся с непонятным поведением. Указанные в таблице 32ns для <ASM>::RDTSCP+CPUID были измерены в Safe mode. В обычном режиме Windows вызов инструкции CPUID увеличивал времена до 350ns(!!!). Похоже, что на проверенных мной машинах с Windows инструкция CPUID выполнялась программно. Наверно это из-за какого-то ПО, установленного на всех моих компьютерах. Разбираться пока не стал, надеюсь, кто-нибудь объяснит это в комментариях.
Как проводились измерения
Утилита запускалась с приоритетом ABOVENORMAL (nice -n -5). Перед всеми измерениями процессор прогревался полсекунды при 100% загрузке рабочего потока. Потом вызвалась функция std::this_thread::yield() это наивная попытка предотвратить переключение контекста если измерение укладывается в один квант времени (к сожалению, это не защищает от системных прерываний). Далее 5 раз вызывались задействованные в тесте функции и данные, чтобы исключить из измерений время подгрузки в кэш процессора. Замеры проводились дважды с разным числом повторов для учёта накладных расходов. Наконец все измерения производились по 20 раз и за результат бралось медианное значение (обычно оно очень близко к минимальному).
Итоги
Никаких открытий сделано не было (и не ожидалось). Для себя сделал выбор функции примерно таким образом (надеюсь на критику профи):
#include <stdint.h>
#ifdef _MSC_VER
# include <intrin.h>
# pragma intrinsic(__rdtscp, _mm_lfence)
#elif defined(__GNUC__)
# include <x86intrin.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
# if defined(_M_X64) || defined(_M_AMD64) || defined(__x86_64__) || defined(__amd64__)
// MSC or GCC on Intel
static inline uint64_t vi_tmGetTicks(void)
{ uint32_t _; // будет удалён оптимизатором
const uint64_t result = __rdtscp(&_);
_mm_lfence();
return result;
}
# elif __ARM_ARCH >= 8 // ARMv8 (RaspberryPi4)
static inline uint64_t vi_tmGetTicks(void)
{ uint64_t result;
__asm__ __volatile__("mrs %0, cntvct_el0" : "=r"(result));
return result;
}
# elif defined(_WIN32) // Windows on other platforms.
static inline uint64_t vi_tmGetTicks(void)
{ LARGE_INTEGER cnt;
QueryPerformanceCounter(&cnt);
return cnt.QuadPart;
}
# elif defined(__linux__) // Linux on other platforms
static inline uint64_t vi_tmGetTicks(void)
{ struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
return 1000000000ULL * ts.tv_sec + ts.tv_nsec;
}
# else
# error "You need to define function(s) for your OS and CPU"
# endif
#ifdef cplusplus
} // extern "C" {
#endif
Буду благодарен за конструктивные замечания.
Спасибо, что дочитали )
P.S.
Ещё пара логов с других машин:
i3-4005U CPU @ 1.70GHz
Start: 2024.05.05 20:11:01
Processor: Intel(R) Core(TM) i3-4005U CPU @ 1.70GHz
Collecting function properties... Done (872.0s )
Measured properties of time functions (median of 5 measurements):
Name:............................................ Discreteness: +/- Duration: +/- One tick: +/- Type:
vvvvvv
<WIN>::timeGetTime():............................ 15.6ms 0.5% 25.0ns 1.7% 1.0ms 0.1% Wall
<WIN>::timeGetSystemTime():...................... 15.6ms 0.5% 29.0ns 0.8% 1.0ms 0.1% Wall
<WIN>::_time64():................................ 1.0s 0.0% 18.0ns 21.0% 1.0s 0.0% Wall
<WIN>::_time32():................................ 1.0s 0.0% 18.0ns 4.6% 1.0s 0.0% Wall
<WIN>::QueryUnbiasedInterruptTimePrecise():...... 100.0ns 0.0% 42.0ns 0.0% 100.0ns 0.0% Wall
<WIN>::QueryUnbiasedInterruptTime():............. 15.6ms 0.0% 15.0ns 38.0% 100.0ns 0.0% Wall
<WIN>::QueryThreadCycleTime():................... 880.0ns 0.2% 910.0ns 0.3% 590.0ps 0.0% Thread
<WIN>::QueryProcessCycleTime():.................. 8.5us 10.0% 8.2us 4.6% 590.0ps 0.0% Process
<WIN>::QueryPerformanceCounter():................ 100.0ns 0.0% 28.0ns 0.0% 100.0ns 0.0% Wall
<WIN>::QueryInterruptTimePrecise():.............. 100.0ns 0.2% 41.0ns 3.6% 100.0ns 0.0% Wall
<WIN>::QueryInterruptTime():..................... 15.6ms 0.0% 2.1ns 86.0% 100.0ns 0.0% Wall
<WIN>::NtQuerySystemTime():...................... 15.6ms 0.0% 4.4ns 23.0% 100.0ns 0.0% Wall
<WIN>::GetTickCount64():......................... 15.6ms 0.0% 14.0ns 57.0% 1.0ms 0.1% Wall
<WIN>::GetTickCount():........................... 15.6ms 0.5% 13.0ns 42.0% 1.0ms 0.1% Wall
<WIN>::GetThreadTimes():......................... 15.6ms 0.0% 890.0ns 0.3% 100.0ns 1.2% Thread
<WIN>::GetSystemTimes() K+U:..................... 8.6ms 7.5% 3.2us 1.4% 25.0ns 0.9% Wall
<WIN>::GetSystemTimePreciseAsFileTime():......... 100.0ns 0.0% 48.0ns 0.0% 100.0ns 0.0% Wall
<WIN>::GetSystemTimeAsFileTime():................ 15.6ms 0.0% 14.0ns 14.0% 100.0ns 0.0% Wall
<WIN>::GetSystemTime():.......................... 15.6ms 0.0% 130.0ns 0.3% 100.0ns 0.1% Wall
<WIN>::GetProcessTimes():........................ 15.6ms 0.0% 1.0us 1.8% 100.0ns 0.0% Process
<WIN>::GetLocalTime():........................... 15.6ms 0.0% 190.0ns 0.3% 100.0ns 0.1% Wall
<C> ::timespec_get(TIME_UTC):................... 100.0ns 0.0% 73.0ns 3.8% 1.0ns 0.0% Wall
<C> ::time():................................... 1.0s 0.0% 18.0ns 4.3% 1.0s 0.0% Wall
<C> ::clock():.................................. 1.0ms 0.0% 85.0ns 4.1% 1.0ms 0.0% Wall
<C++>::system_clock::now():...................... 100.0ns 0.0% 45.0ns 0.0% 100.0ns 0.0% Wall
<C++>::steady_clock::now():...................... 100.0ns 0.0% 36.0ns 0.0% 1.0ns 0.0% Wall
<C++>::high_resolution_clock::now():............. 100.0ns 0.0% 36.0ns 0.0% 1.0ns 0.0% Wall
<BST>::cpu_timer.elapsed() wall:................. 1.1us 0.1% 1.1us 3.0% 1.0ns 0.0% Wall
<BST>::cpu_timer.elapsed() user + system:........ 15.6ms 0.0% 1.1us 0.1% 1.0ns 0.0% Process
<ASM>::RDTSC_INTRINSIC:.......................... 14.2ns 0.0% 14.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::RDTSC_ASM:................................ 14.2ns 1.0% 14.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::RDTSCP_INTRINSIC:......................... 19.5ns 0.0% 18.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::RDTSCP_ASM:............................... 21.2ns 1.1% 19.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::RDTSCP+LFENCE_INTRINSIC:.................. 32.6ns 0.0% 31.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::RDTSCP+LFENCE_ASM:........................ 33.6ns 0.0% 33.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::RDTSCP+CPUID_INTRINSIC:................... 79.0ns 0.0% 79.0ns 1.2% 590.0ps 0.0% Wall
<ASM>::RDTSCP+CPUID_ASM:......................... 80.1ns 0.0% 79.0ns 0.1% 590.0ps 0.0% Wall
<ASM>::MFENCE+LFENCE+RDTSC_INTRINSIC:............ 36.8ns 0.0% 39.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::MFENCE+LFENCE+RDTSC_ASM:.................. 40.7ns 0.0% 40.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::LFENCE+RDTSC_INTRINSIC:................... 21.1ns 2.2% 19.0ns 5.4% 590.0ps 0.0% Wall
<ASM>::LFENCE+RDTSC_ASM:......................... 22.4ns 0.0% 21.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::CPUID+RDTSC_INTRINSIC:.................... 64.9ns 0.7% 67.0ns 0.0% 590.0ps 0.0% Wall
<ASM>::CPUID+RDTSC_ASM:.......................... 68.4ns 0.0% 67.0ns 0.0% 590.0ps 0.0% Wall
Notes:
The std::chrono::system_clock::is_steady: false;
The std::chrono::steady_clock::is_steady: true;
The std::chrono::high_resolution_clock::is_steady: true;
The std::chrono::system_clock::duration: 100.0ns;
The std::chrono::steady_clock::duration: 1.0ns;
The std::chrono::high_resolution_clock::duration: 1.0ns;
The number of clock vi_tmGetTicks per second 'CLOCKS_PER_SEC': 1'000 (what is equivalent to 1.0ms)
Frequency of the performance counter 'QueryPerformanceFrequency()': 10'000'000 (what is equivalent to 100.0ns);
The timer resolution 'NtQueryTimerResolution()': 15.6ms. (from 0.5 to 15.6ms);
The timer device to determine its resolution 'timeGetDevCaps()': from 1 to 1'000'000ms.;
Periodic time adjustments 'GetSystemTimeAdjustment()':
TimeAdjustment = 15.6ms;
TimeIncrement = 15.6ms;
TimeAdjustmentDisabled = true;
Time expend: 00:14:33
Cortex-A72 1.8GHz (Raspberry Pi 4B)
Start: 2024.05.29 16:08:46
Model name: Cortex-A72
CPU max MHz: 1800.0000
CPU min MHz: 600.0000
Computer: 'RaspberryPi4'
Collecting function properties... Done (1.5ks)
Measured properties of time functions (median of 20 measurements):
Name:............................................ Discreteness: +/- Duration: +/- One tick: +/- Type:
vvvvvv
<LNX>::times(tms):............................... 10.0ms 0.0% 870.0ns 0.5% 10.0ms 0.8% Process
<LNX>::times(nullptr):........................... 10.0ms 0.0% 400.0ns 0.0% 10.0ms 0.0% Wall
<LNX>::gettimeofday():........................... 1.0us 0.0% 44.0ns 0.3% 1.0us 0.0% Wall
<LNX>::getrusage(RUSAGE_THREAD):................. 4.0ms 0.0% 680.0ns 0.3% 1.0us 0.0% Thread
<LNX>::getrusage(RUSAGE_SELF):................... 1.1us 0.4% 1.1us 0.2% 1.0us 0.0% Process
<LNX>::clock_gettime(CLOCK_THREAD_CPUTIME_ID):... 747.0ns 0.9% 750.0ns 0.3% 1.0ns 0.0% Thread
<LNX>::clock_gettime(CLOCK_TAI):................. 48.3ns 0.1% 45.0ns 0.1% 1.0ns 0.0% Wall
<LNX>::clock_gettime(CLOCK_REALTIME_COARSE):..... 4.0ms 0.0% 18.0ns 0.2% 1.0ns 0.0% Wall
<LNX>::clock_gettime(CLOCK_REALTIME):............ 48.3ns 0.1% 45.0ns 0.3% 1.0ns 0.0% Wall
<LNX>::clock_gettime(CLOCK_PROCESS_CPUTIME_ID):.. 773.0ns 0.9% 790.0ns 0.5% 1.0ns 0.0% Process
<LNX>::clock_gettime(CLOCK_MONOTONIC_RAW):....... 48.5ns 7.0% 46.0ns 12.0% 1.0ns 0.0% Wall
<LNX>::clock_gettime(CLOCK_MONOTONIC_COARSE):.... 4.0ms 0.0% 18.0ns 0.2% 1.0ns 0.0% Wall
<LNX>::clock_gettime(CLOCK_MONOTONIC):........... 48.3ns 0.2% 45.0ns 0.2% 1.0ns 0.0% Wall
<LNX>::clock_gettime(CLOCK_BOOTTIME):............ 48.3ns 0.2% 45.0ns 2.8% 1.0ns 0.0% Wall
<C> ::timespec_get(TIME_UTC):................... 47.3ns 9.5% 44.0ns 12.0% 1.0ns 0.0% Wall
<C> ::time():................................... 1.0s 0.0% 22.0ns 2.0% 1.0s 0.0% Wall
<C> ::clock():.................................. 1.0us 0.0% 780.0ns 0.3% 1.0us 0.0% Process
<C++>::system_clock::now():...................... 46.7ns 12.0% 44.0ns 7.6% 1.0ns 0.0% Wall
<C++>::steady_clock::now():...................... 46.7ns 13.0% 43.0ns 0.6% 1.0ns 0.0% Wall
<C++>::high_resolution_clock::now():............. 46.7ns 12.0% 43.0ns 1.3% 1.0ns 0.0% Wall
<ASM>::MRS:...................................... 18.9ns 0.2% 5.0ns 0.7% 19.0ns 0.0% Wall
Notes:
The std::chrono::system_clock::is_steady: false;
The std::chrono::steady_clock::is_steady: true;
The std::chrono::high_resolution_clock::is_steady: false;
The std::chrono::system_clock::duration: 1.0ns;
The std::chrono::steady_clock::duration: 1.0ns;
The std::chrono::high_resolution_clock::duration: 1.0ns;
The number of clock vi_tmGetTicks per second 'CLOCKS_PER_SEC': 1'000'000 (what is equivalent to 1.0us)
The number of clock vi_tmGetTicks per second 'sysconf(_SC_CLK_TCK)': 100 (10ms);
Resolution (precision) 'clock_getres(CLOCK_REALTIME)': 1.0ns;
Resolution (precision) 'clock_getres(CLOCK_MONOTONIC)': 1.0ns;
Resolution (precision) 'clock_getres(CLOCK_PROCESS_CPUTIME_ID)': 1.0ns;
Resolution (precision) 'clock_getres(CLOCK_THREAD_CPUTIME_ID)': 1.0ns;
Time expend: 00:25:14
Комментарии (13)
unreal_undead2
03.06.2024 05:46+1Похоже, что на проверенных мной машинах с Windows инструкция CPUID выполнялась программно.
Включенный Hyper-V ?
tixo Автор
03.06.2024 05:46надеюсь, кто-нибудь объяснит это в комментариях
Не зря надеялся )
Наверно, на всех машинах установлен как минимум MS Sandbox. Но для меня неожиданно, что включение Hyper-V влияет на процессы запущенные прямо на хосте, а не в VM.
IMHO с такими задержками CPUID для профилирования не годится. Но если погуглить RDTSC, то чаще всего для сериализации инструкций предлагают именно его.unreal_undead2
03.06.2024 05:46+2Hyper V - гипервизор первого типа, так что при включении "хостовая" Windows реально бежит под ним.
А так особого смысла в сериализации не вижу. Если делать отсечки до и после более-менее тяжёлого цикла - особой разницы (в процентах от полученного значения) в измерениях не будет, если же вставлять измерение внутрь цикла - даже сама по себе rdtsc будет влиять на скорость работы и результат измерений, с сериализацией будет ещё хуже - так что получить осмысленный результат, по которому можно делать выводы о работе кода без измеряющей обвзяки, достаточно сложно.
SpiderEkb
03.06.2024 05:46Со временем - тут у кого на что фантазии хватит...
Под DOS, помнится, еще в 90-х года был RTC - real-time clock который "тикал" 1024 раза в секунду и мог дергать соответствующее прерывание (номер уже не вспомню за давностью лет, помню, что оно было практически не документировано и разбираться сними приходилось перерыв кучу информации).
Не так давно писал тут про Standard Time на платформе IBM i - тоже достаточно нестандартное решение... Точность 1мкс (плюс есть еще 12 "бит уникальности").
unreal_undead2
03.06.2024 05:46Всё таки максимальная частота таймера на IBM PC около мегагерца - https://en.wikibooks.org/wiki/X86_Assembly/Programmable_Interval_Timer
SpiderEkb
03.06.2024 05:46Я несколько отошел от x86 архитектуры в последние годы.
Помнится, под виндой еще со времен 3.11 был multimedia timer который как минимум миллисекунды достаточно точно считал.
Но сильно глубоко в это не вникал - просто не попадалось задач где нужно было бы точно измерять абсолютное время (или временные интервалы)
Gumanoid
03.06.2024 05:46clock_gettime(CLOCK_MONOTONIC_RAW) [Linux] – видимо лучший вариант для тайминга в LinuxAPI. Точность и время выполнения около 22ns.
Только если запускать нативно. В клауде, в зависимости от настроек гипервизора, ситуация может быть другой. Например на моей системе (под KVM)
CLOCK_MONOTONIC
выполняется за 30 нс, а вотCLOCK_MONOTONIC_RAW
за 400 нс (делает системный вызов).tixo Автор
03.06.2024 05:46Интересно!
Прогнал утилиту на VPS под KVM:Частота процессора в два раза ниже, и время в два раза больше.
randomfoto
03.06.2024 05:46Попросил DALL-E нарисовать программиста, выбирающего секундомер. Вот результат )
Программист думает: И как я этой кучей безменов буду измерять время?
jaha33
А на винде точность измерения разве не зависит от материнской платы и самой версии windows? Несколько лет назад сталкивался необходимостью замера интервалов в мкс на windows, точно замера плавала на десятки мкс, как выяснилось, для более точных замеров нужна win8 с какой версией обновления и какие то определённые мат платы
Пробовал измерять сначала функциями c#, потом win api
tixo Автор
Это зависит от функции. Windows "загрубляет" дискретность ряда функций до 100ns, но если не может обеспечить и такую точность то, например, на старом 32-х битном Intel Atom Z2760@1.80GHz дискретность всех измеренных мной функций оказалась не лучше 570ns. Т.е. QueryPerformanceCounter при документированных единицах измерения 100ns смогла измерить интервал времени лишь в 5,7 раза длиннее. Такую же дискретность на этой машине выдали и все современные стандартные функции С и С++.
Многие ф-ии WinAPI (например GetSystemTime) завязаны на частоту прерывания системных часов которая по умолчанию равна 64 тикам в секунду независимо от железа, но может изменяться программно с помощью ExSetTimerResolution.
Кстати, частота прерывания системных часов может изменяться, например, музыкальным или видео плеером, что иногда приводит к неприятным сюрпризам.