From b1870bd709cb1e1b8353df1c115529bbb324716a Mon Sep 17 00:00:00 2001 From: Mikhail Romanko Date: Sun, 21 Sep 2025 22:07:54 +0300 Subject: Add timers for measuring elapsed time --- CMakeLists.txt | 3 + configure | 9 ++- doc/Manual/en/BH_Timer.pod | 82 ++++++++++++++++++++++++++ doc/Manual/en/Makefile | 2 + doc/Manual/ru/BH_Timer.pod | 83 +++++++++++++++++++++++++++ doc/Manual/ru/Makefile | 2 + include/BH/Timer.h | 32 +++++++++++ src/Platform/Dummy/Timer.c | 50 ++++++++++++++++ src/Platform/Posix/Timer.c | 139 +++++++++++++++++++++++++++++++++++++++++++++ src/Platform/Win32/Timer.c | 130 ++++++++++++++++++++++++++++++++++++++++++ test/src/TestTimer.c | 36 ++++++++++++ 11 files changed, 565 insertions(+), 3 deletions(-) create mode 100644 doc/Manual/en/BH_Timer.pod create mode 100644 doc/Manual/ru/BH_Timer.pod create mode 100644 include/BH/Timer.h create mode 100644 src/Platform/Dummy/Timer.c create mode 100644 src/Platform/Posix/Timer.c create mode 100644 src/Platform/Win32/Timer.c create mode 100644 test/src/TestTimer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5450975..d6d50ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,14 +102,17 @@ file(GLOB BH_HEADER set(BH_SOURCE_DUMMY src/Platform/Dummy/File.c + src/Platform/Dummy/Timer.c ) set(BH_SOURCE_WIN32 src/Platform/Win32/File.c + src/Platform/Win32/Timer.c ) set(BH_SOURCE_POSIX src/Platform/Posix/File.c + src/Platform/Posix/Timer.c ) set(BH_SOURCE_WIN32_MT diff --git a/configure b/configure index 903cbf2..c4e1d03 100755 --- a/configure +++ b/configure @@ -164,9 +164,12 @@ done add_source "${source_path}src/Platform/Spinlock.c" case "$platform" in - Posix) add_source "${source_path}src/Platform/Posix/File.c" ;; - Win32) add_source "${source_path}src/Platform/Win32/File.c" ;; - *) add_source "${source_path}src/Platform/Dummy/File.c" ;; + Posix) add_source "${source_path}src/Platform/Posix/File.c" ; + add_source "${source_path}src/Platform/Posix/Timer.c" ;; + Win32) add_source "${source_path}src/Platform/Win32/File.c" ; + add_source "${source_path}src/Platform/Win32/Timer.c" ;; + *) add_source "${source_path}src/Platform/Dummy/File.c" ; + add_source "${source_path}src/Platform/Dummy/Timer.c" ;; esac if [ "$enable_mt" = "yes" ]; then diff --git a/doc/Manual/en/BH_Timer.pod b/doc/Manual/en/BH_Timer.pod new file mode 100644 index 0000000..56e79fe --- /dev/null +++ b/doc/Manual/en/BH_Timer.pod @@ -0,0 +1,82 @@ +=encoding UTF-8 + + +=head1 NAME + +BH_Timer - timer utilities + + +=head1 SYNTAX + + #include + + cc prog.c -o prog -lbh + + +=head1 DESCRIPTION + +This module provides a simple high-resolution timer interface for measuring +elapsed time. The timer uses a monotonic clock source when available, making it +suitable for accurate time measurements, such as profiling or timeouts. It +supports millisecond and nanosecond precision and can be started, restarted, +and queried for elapsed time. + + + +=head1 API CALLS + + +=head2 BH_TimerNew + + BH_Timer *BH_TimerNew(void); + +Creates and initializes a new timer object. + +Returns a pointer to a newly allocated BH_Timer on success, or NULL on failure. + + +=head2 BH_TimerFree + + void BH_TimerFree(BH_Timer *timer); + +Destroys the timer. + +=head2 BH_TimerIsMonotonic + + int BH_TimerIsMonotonic(BH_Timer *timer); + +Checks whether the timer uses a monotonic clock source. + + +=head2 BH_TimerStart + + void BH_TimerStart(BH_Timer *timer); + +Starts or resets the timer to begin counting from the current time. + + +=head2 BH_TimerRestart + + int64_t BH_TimerRestart(BH_Timer *timer); + +Restarts the timer and returns the number of milliseconds that had elapsed since +the start of the timer. + + +=head2 BH_TimerMilliseconds + + int64_t BH_TimerMilliseconds(BH_Timer *timer); + +Returns the number of milliseconds that had elapsed since the start of the +timer. + + +=head2 BH_TimerNanoseconds + + int64_t BH_TimerNanoseconds(BH_Timer *timer); + +Returns the number of nanoseconds that had elapsed since the start of the timer. + +=head1 SEE ALSO + +L diff --git a/doc/Manual/en/Makefile b/doc/Manual/en/Makefile index f5ff1f7..b2fd873 100644 --- a/doc/Manual/en/Makefile +++ b/doc/Manual/en/Makefile @@ -20,6 +20,7 @@ HTMLS = BH_Algo.html \ BH_Ray3f.html \ BH_String.html \ BH_Thread.html \ + BH_Timer.html \ BH_Unicode.html \ BH_Util.html \ BH_Vec2f.html \ @@ -48,6 +49,7 @@ MANS = BH_Algo.3 \ BH_Ray3f.3 \ BH_String.3 \ BH_Thread.3 \ + BH_Timer.3 \ BH_Unicode.3 \ BH_Util.3 \ BH_Vec2f.3 \ diff --git a/doc/Manual/ru/BH_Timer.pod b/doc/Manual/ru/BH_Timer.pod new file mode 100644 index 0000000..290ec11 --- /dev/null +++ b/doc/Manual/ru/BH_Timer.pod @@ -0,0 +1,83 @@ +=encoding UTF-8 + + +=head1 НАЗВАНИЕ + +BH_Timer - утилиты таймера + + +=head1 СИНТАКСИС + + #include + + cc prog.c -o prog -lbh + + +=head1 ОПИСАНИЕ + +Этот модуль предоставляет простой интерфейс высокоточного таймера для измерения +прошедшего времени. По возможности таймер использует монотонный источник часов, +что делает его подходящим для точных измерений времени, например, профилирования +или тайм-аутов. Поддерживается точность в миллисекундах и наносекундах; таймер +можно запускать, перезапускать и запрашивать прошедшее время. + + +=head1 API ВЫЗОВЫ + + +=head2 BH_TimerNew + + BH_Timer *BH_TimerNew(void); + +Создаёт и инициализирует новый объект таймера. + +В случае успеха функция возвращает указатель на новый объект BH_Timer, +или NULL в случае ошибки. + + +=head2 BH_TimerFree + + void BH_TimerFree(BH_Timer *timer); + +Уничтожает таймер. + + +=head2 BH_TimerIsMonotonic + + int BH_TimerIsMonotonic(BH_Timer *timer); + +Проверяет, использует ли таймер монотонный источник времени. + + +=head2 BH_TimerStart + + void BH_TimerStart(BH_Timer *timer); + +Запускает или сбрасывает таймер, начиная отсчёт с текущего момента времени. + + +=head2 BH_TimerRestart + + int64_t BH_TimerRestart(BH_Timer *timer); + +Перезапускает таймер и возвращает количество миллисекунд, прошедших с момента +его запуска. + + +=head2 BH_TimerMilliseconds + + int64_t BH_TimerMilliseconds(BH_Timer *timer); + +Возвращает количество миллисекунд, прошедших с момента запуска таймера. + + +=head2 BH_TimerNanoseconds + + int64_t BH_TimerNanoseconds(BH_Timer *timer); + +Возвращает количество наносекунд, прошедших с момента запуска таймера. + + +=head1 СМ. ТАКЖЕ + +L diff --git a/doc/Manual/ru/Makefile b/doc/Manual/ru/Makefile index f5ff1f7..b2fd873 100644 --- a/doc/Manual/ru/Makefile +++ b/doc/Manual/ru/Makefile @@ -20,6 +20,7 @@ HTMLS = BH_Algo.html \ BH_Ray3f.html \ BH_String.html \ BH_Thread.html \ + BH_Timer.html \ BH_Unicode.html \ BH_Util.html \ BH_Vec2f.html \ @@ -48,6 +49,7 @@ MANS = BH_Algo.3 \ BH_Ray3f.3 \ BH_String.3 \ BH_Thread.3 \ + BH_Timer.3 \ BH_Unicode.3 \ BH_Util.3 \ BH_Vec2f.3 \ diff --git a/include/BH/Timer.h b/include/BH/Timer.h new file mode 100644 index 0000000..51ab0de --- /dev/null +++ b/include/BH/Timer.h @@ -0,0 +1,32 @@ +#ifndef BH_TIMER_H +#define BH_TIMER_H + + +#include "Common.h" + + +typedef struct BH_Timer BH_Timer; + + +BH_Timer *BH_TimerNew(void); + + +void BH_TimerFree(BH_Timer *timer); + + +int BH_TimerIsMonotonic(BH_Timer *timer); + + +void BH_TimerStart(BH_Timer *timer); + + +int64_t BH_TimerRestart(BH_Timer *timer); + + +int64_t BH_TimerMilliseconds(BH_Timer *timer); + + +int64_t BH_TimerNanoseconds(BH_Timer *timer); + + +#endif /* BH_TIMER_H */ diff --git a/src/Platform/Dummy/Timer.c b/src/Platform/Dummy/Timer.c new file mode 100644 index 0000000..af70274 --- /dev/null +++ b/src/Platform/Dummy/Timer.c @@ -0,0 +1,50 @@ +#include + + +BH_Timer *BH_TimerNew(void) +{ + return NULL; +} + + +void BH_TimerFree(BH_Timer *timer) +{ + BH_UNUSED(timer); +} + + +int BH_TimerIsMonotonic(BH_Timer *timer) +{ + BH_UNUSED(timer); + return 0; +} + + +void BH_TimerStart(BH_Timer *timer) +{ + BH_UNUSED(timer); +} + + +int64_t BH_TimerRestart(BH_Timer *timer) +{ + BH_UNUSED(timer); + + return 0; +} + + +int64_t BH_TimerMilliseconds(BH_Timer *timer) +{ + BH_UNUSED(timer); + + return 0; +} + + +int64_t BH_TimerNanoseconds(BH_Timer *timer) +{ + BH_UNUSED(timer); + + return 0; +} diff --git a/src/Platform/Posix/Timer.c b/src/Platform/Posix/Timer.c new file mode 100644 index 0000000..0b68036 --- /dev/null +++ b/src/Platform/Posix/Timer.c @@ -0,0 +1,139 @@ +#include + +#include +#include +#include +#include + + +#define TYPE_OLD_REALTIME 0x0000 +#define TYPE_NEW_REALTIME 0x0001 +#define TYPE_MONOTONIC 0x0002 + + +struct BH_Timer +{ + int type; + int64_t sec; + int64_t nsec; +}; + + +static int checkAvailableTimer(void) +{ +#if (_POSIX_TIMERS > 0) || defined(BH_USE_CLOCK_GETTIME) + struct timespec ts; + + if (!clock_gettime(CLOCK_MONOTONIC, &ts)) + return TYPE_MONOTONIC; + + if (!clock_gettime(CLOCK_REALTIME, &ts)) + return TYPE_NEW_REALTIME; +#endif + + return TYPE_OLD_REALTIME; +} + + +static void getTimer(int type, + int64_t *sec, + int64_t *nsec) +{ + struct timespec ts; + struct timeval tv; + + switch (type) + { +#if (_POSIX_TIMERS > 0) || defined(BH_USE_CLOCK_GETTIME) + case TYPE_MONOTONIC: + clock_gettime(CLOCK_MONOTONIC, &ts); + *sec = ts.tv_sec; + *nsec = ts.tv_nsec; + break; + + case TYPE_NEW_REALTIME: + clock_gettime(CLOCK_REALTIME, &ts); + *sec = ts.tv_sec; + *nsec = ts.tv_nsec; + break; +#endif + + default: + case TYPE_OLD_REALTIME: + gettimeofday(&tv, NULL); + *sec = tv.tv_sec; + *nsec = tv.tv_usec * (uint64_t)1000; + break; + } +} + + +BH_Timer *BH_TimerNew(void) +{ + BH_Timer *result; + + result = malloc(sizeof(*result)); + if (result) + { + result->type = checkAvailableTimer(); + BH_TimerStart(result); + } + + return result; +} + + +void BH_TimerFree(BH_Timer *timer) +{ + free(timer); +} + + +int BH_TimerIsMonotonic(BH_Timer *timer) +{ + return timer->type == TYPE_MONOTONIC; +} + + +void BH_TimerStart(BH_Timer *timer) +{ + getTimer(timer->type, &timer->sec, &timer->nsec); +} + + +int64_t BH_TimerRestart(BH_Timer *timer) +{ + int64_t oldSec, oldNsec; + + oldSec = timer->sec; oldNsec = timer->nsec; + + getTimer(timer->type, &timer->sec, &timer->nsec); + oldSec = timer->sec - oldSec; + oldNsec = timer->nsec - oldNsec; + + return (oldSec * 1000) + (oldNsec / 1000000); +} + + +int64_t BH_TimerMilliseconds(BH_Timer *timer) +{ + int64_t newSec, newNsec; + + getTimer(timer->type, &newSec, &newNsec); + newSec -= timer->sec; + newNsec -= timer->nsec; + + return (newSec * 1000) + (newNsec / 1000000); +} + + +int64_t BH_TimerNanoseconds(BH_Timer *timer) +{ + int64_t newSec, newNsec; + + getTimer(timer->type, &newSec, &newNsec); + newSec -= timer->sec; + newNsec -= timer->nsec; + + return (newSec * 1000000000) + newNsec; +} diff --git a/src/Platform/Win32/Timer.c b/src/Platform/Win32/Timer.c new file mode 100644 index 0000000..03d4d7f --- /dev/null +++ b/src/Platform/Win32/Timer.c @@ -0,0 +1,130 @@ +#include + +#include +#include + + +#define TYPE_TICKS 0x0000 +#define TYPE_COUNTER 0x0001 + + +struct BH_Timer +{ + int type; + LARGE_INTEGER frequency; + int64_t sec; + int64_t nsec; +}; + + +static int checkAvailableTimer(LARGE_INTEGER *frequency) +{ + LARGE_INTEGER dummy; + + if (QueryPerformanceFrequency(frequency)) + { + if (QueryPerformanceCounter(&dummy)) + { + return TYPE_COUNTER; + } + } + + return TYPE_TICKS; +} + + +static void getTimer(int type, + LARGE_INTEGER *frequency, + int64_t *sec, + int64_t *nsec) +{ + LARGE_INTEGER newCount; + int64_t newTicks; + + switch (type) + { + case TYPE_COUNTER: + QueryPerformanceCounter(&newCount); + *sec = newCount.QuadPart / frequency->QuadPart; + *nsec = (newCount.QuadPart - *sec * frequency->QuadPart) * 1000000000 / frequency->QuadPart; + break; + + default: + case TYPE_TICKS: + newTicks = GetTickCount64(); + *sec = newTicks / 1000; + *nsec = (newTicks - *sec * 1000) * 1000000; + break; + } +} + + +BH_Timer *BH_TimerNew(void) +{ + BH_Timer *result; + + result = malloc(sizeof(*result)); + if (result) + { + result->type = checkAvailableTimer(&result->frequency); + BH_TimerStart(result); + } + + return result; +} + + +void BH_TimerFree(BH_Timer *timer) +{ + free(timer); +} + + +int BH_TimerIsMonotonic(BH_Timer *timer) +{ + return timer->type == TYPE_COUNTER; +} + + +void BH_TimerStart(BH_Timer *timer) +{ + getTimer(timer->type, &timer->frequency, &timer->sec, &timer->nsec); +} + + +int64_t BH_TimerRestart(BH_Timer *timer) +{ + int64_t oldSec, oldNsec; + + oldSec = timer->sec; oldNsec = timer->nsec; + + getTimer(timer->type, &timer->frequency, &timer->sec, &timer->nsec); + oldSec = timer->sec - oldSec; + oldNsec = timer->nsec - oldNsec; + + return (oldSec * 1000) + (oldNsec / 1000000); +} + + +int64_t BH_TimerMilliseconds(BH_Timer *timer) +{ + int64_t newSec, newNsec; + + getTimer(timer->type, &timer->frequency, &newSec, &newNsec); + newSec -= timer->sec; + newNsec -= timer->nsec; + + return (newSec * 1000) + (newNsec / 1000000); +} + + +int64_t BH_TimerNanoseconds(BH_Timer *timer) +{ + int64_t newSec, newNsec; + + getTimer(timer->type, &timer->frequency, &newSec, &newNsec); + newSec -= timer->sec; + newNsec -= timer->nsec; + + return (newSec * 1000000000) + newNsec; +} diff --git a/test/src/TestTimer.c b/test/src/TestTimer.c new file mode 100644 index 0000000..b08dba8 --- /dev/null +++ b/test/src/TestTimer.c @@ -0,0 +1,36 @@ +#include +#include +#include + + +BH_UNIT_TEST(General) +{ + BH_Timer *timer; + + BH_VERIFY((timer = BH_TimerNew()) != NULL); + BH_ThreadSleep(5000); + + BH_VERIFY(BH_TimerMilliseconds(timer) >= 5000); + BH_VERIFY(BH_TimerNanoseconds(timer) >= 5000000000); + BH_VERIFY(BH_TimerRestart(timer) >= 5000); + BH_VERIFY(BH_TimerRestart(timer) < 5000); + + BH_ThreadSleep(5000); + BH_VERIFY(BH_TimerMilliseconds(timer) >= 5000); + BH_TimerStart(timer); + BH_VERIFY(BH_TimerMilliseconds(timer) < 5000); + + BH_TimerFree(timer); + return 0; +} + + +int main(int argc, char **argv) +{ + (void)argc; + (void)argv; + + BH_UNIT_ADD(General); + + return BH_UnitRun(); +} -- cgit v1.2.3