Высокоточное время на Apple Silicon: решение для Delphi и Pascal
В мире разработки на Delphi и Pascal часто возникает необходимость в измерении времени с высокой точностью. Стандартные функции, такие как GetTickCount64, могут быть недостаточно точными, особенно на современных платформах, таких как MacOS на процессорах ARM (Apple Silicon). В этой статье мы рассмотрим проблему получения высокоточного времени в этой среде и предложим решение, основанное на обсуждении, развернувшемся на форуме Lazarus.
Проблема:
Функция GetTickCount64 в SysUtils предоставляет довольно грубое измерение времени (1 мс). Для задач, требующих большей точности, этого недостаточно. На Apple Silicon, как и на других современных платформах, существуют аппаратные таймеры, способные предоставлять время с наносекундной точностью. Однако доступ к ним напрямую затруднен, так как они абстрагированы операционной системой.
Решение:
Обсуждение на форуме Lazarus указывает на несколько возможных решений, основанных на использовании API MacOS:
mach_absolute_time(): Эта функция предоставляет доступ к непрерывно увеличивающемуся счетчику с наносекундным разрешением. Для преобразования "тиков" в наносекунды необходимо использовать функцию mach_timebase_info(), которая возвращает информацию о временной базе системы.
clock_gettime(): POSIX-совместимая функция, предоставляющая доступ к различным системным часам с возможностью указания требуемой точности. Подходит для переносимого кода.
dispatch_time(): Часть Grand Central Dispatch, предназначенная для планирования задач, но также может быть использована для измерения времени.
Пример кода на Object Pascal (Delphi):
Предложенное на форуме решение, использующее mach_absolute_time(), выглядит следующим образом:
{$mode objfpc}
{$modeswitch objectivec1}
{$linkframework CoreServices} // Необходимо для линковки с CoreServices
program MachTimeExample;
uses
ctypes, MacOSAll, CocoaAll;
var
timebase: mach_timebase_info_data_t;
startTime, endTime, elapsedNano: UInt64;
begin
// Инициализация информации о временной базе
if mach_timebase_info(@timebase) <> 0 then
begin
writeln('Ошибка получения информации о временной базе');
Exit;
end;
// Получение начального времени
startTime := mach_absolute_time();
// Выполнение измеряемого кода
NSProcessInfo.processInfo.sleepForTimeInterval(0.5); // Задержка на 0.5 секунды
// Получение конечного времени
endTime := mach_absolute_time();
// Вычисление прошедшего времени в наносекундах
elapsedNano := (endTime - startTime) * UInt64(timebase.numer) div UInt64(timebase.denom);
writeln('Начальное время: ', startTime, ' тиков');
writeln('Конечное время: ', endTime, ' тиков');
writeln('Прошло времени: ', elapsedNano, ' наносекунд');
writeln(' ', elapsedNano / 1000000:0:3, ' миллисекунд');
end.
Пояснения к коду:
{$mode objfpc} и {$modeswitch objectivec1}: Необходимы для компиляции кода, использующего Objective-C.
{$linkframework CoreServices}: Указывает компилятору на необходимость линковки с фреймворком CoreServices, содержащим необходимые функции.
mach_timebase_info_data_t: Структура, содержащая информацию о временной базе. Определена в модуле MacOSAll.
mach_timebase_info(@timebase): Вызывает функцию mach_timebase_info для заполнения структуры timebase. Проверка возвращаемого значения позволяет обработать возможную ошибку.
mach_absolute_time(): Возвращает текущее значение счетчика времени.
NSProcessInfo.processInfo.sleepForTimeInterval(0.5): Пример кода, который нужно измерить. В данном случае, это задержка на 0.5 секунды. В реальном приложении здесь будет ваш код.
elapsedNano := (endTime - startTime) * UInt64(timebase.numer) div UInt64(timebase.denom): Вычисляет разницу между начальным и конечным временем в тиках, а затем преобразует ее в наносекунды, используя информацию из структуры timebase.
Альтернативное решение (использование clock_gettime()):
Использование clock_gettime() может быть более переносимым решением, так как это POSIX-стандарт. Пример кода:
{$mode objfpc}
uses
BaseUnix, Unix;
type
timespec = record
tv_sec: time_t;
tv_nsec: longint;
end;
function clock_gettime(clock_id: clockid_t; var tp: timespec): longint; cdecl; external libc name 'clock_gettime';
const
CLOCK_MONOTONIC = 1; // Наиболее подходящий вариант для измерения времени
var
ts: timespec;
startTime, endTime: int64;
elapsedNano: int64;
begin
// Получение начального времени
clock_gettime(CLOCK_MONOTONIC, ts);
startTime := ts.tv_sec * 1000000000 + ts.tv_nsec;
// Выполнение измеряемого кода
Sleep(500); // Задержка на 500 миллисекунд
// Получение конечного времени
clock_gettime(CLOCK_MONOTONIC, ts);
endTime := ts.tv_sec * 1000000000 + ts.tv_nsec;
// Вычисление прошедшего времени в наносекундах
elapsedNano := endTime - startTime;
writeln('Прошло времени: ', elapsedNano, ' наносекунд');
writeln(' ', elapsedNano / 1000000:0:3, ' миллисекунд');
end.
Пояснения к альтернативному коду:
BaseUnix, Unix: Необходимые модули для работы с POSIX API.
timespec: Структура, представляющая время в секундах и наносекундах.
clock_gettime: Функция POSIX API для получения текущего времени.
CLOCK_MONOTONIC: Указывает на использование монотонного таймера, который не подвержен корректировкам системного времени. Это наиболее подходящий вариант для измерения времени.
Преобразование времени из timespec в наносекунды: ts.tv_sec * 1000000000 + ts.tv_nsec.
Важные замечания:
Зависимость от платформы: Предложенные решения зависят от MacOS и POSIX API. Для обеспечения переносимости кода необходимо использовать условную компиляцию ({$IFDEF ...}) и предоставлять альтернативные реализации для других платформ.
Точность: Несмотря на то, что API предоставляют наносекундное разрешение, реальная точность измерений может быть ниже из-за ограничений аппаратного обеспечения и операционной системы.
Нагрузка на систему: Частые вызовы функций измерения времени могут создавать дополнительную нагрузку на систему. Следует использовать их только тогда, когда это действительно необходимо.
Версия компилятора: Для успешной компиляции кода, использующего Objective-C, может потребоваться более новая версия Free Pascal Compiler (FPC), например, 3.3.1 или выше.
Заключение:
Получение высокоточного времени на Apple Silicon в Delphi и Pascal возможно с использованием API MacOS, таких как mach_absolute_time() или clock_gettime(). Предложенные примеры кода демонстрируют основные принципы работы с этими API. При использовании этих решений необходимо учитывать зависимость от платформы, реальную точность измерений и возможную нагрузку на систему. Выбор между mach_absolute_time() и clock_gettime() зависит от конкретных требований к переносимости и точности измерений.
В статье рассматривается проблема получения высокоточного времени в Delphi и Pascal на Apple Silicon и предлагаются решения с использованием API MacOS, таких как `mach_absolute_time()` и `clock_gettime()`.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.