Почему ТекущаяУниверсальнаяДатаВМиллисекундах() в 1С «перескакивает» на 15 миллисекунд и как повысить точность измерения времени?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке высокопроизводительных механизмов, замере времени выполнения операций или создании сложных алгоритмов в 1С программисты часто используют встроенную функцию ТекущаяУниверсальнаяДатаВМиллисекундах(). Однако при детальном анализе результатов работы этой функции в цикле можно столкнуться с крайне странным поведением: время идет не плавно (по 1 миллисекунде), а совершает резкие скачки — «стыки» величиной ровно в 15–16 миллисекунд. На протяжении этого интервала функция возвращает абсолютно одинаковое значение.

В этой статье мы подробно разберем, куда «исчезают» эти 15 миллисекунд, проанализируем гипотезы разработчиков, выясним истинную причину такого поведения на уровне операционной системы и рассмотрим практические методы решения этой проблемы.

Суть проблемы: скачкообразное получение времени

Рассмотрим классический эксперимент, который проводит автор темы. Мы создаем динамический массив и в быстром цикле заполняем его текущими значениями времени в миллисекундах:


Массив = Новый Массив;
Для А = 1 По 10000 Цикл
    Массив.Добавить(ТекущаяУниверсальнаяДатаВМиллисекундах());
КонецЦикла;

При анализе полученного массива обнаруживается следующая картина: сначала идет длинная последовательность одинаковых чисел (например, 63 665 076 297 099), а затем происходит мгновенный скачок, и все последующие элементы заполняются значением 63 665 076 297 114. Разница между этими двумя значениями составляет ровно 15 миллисекунд.

Возникает логичный вопрос: почему быстродействующий процессор, который успевает за 15 миллисекунд выполнить тысячи итераций цикла, не получает промежуточных значений времени?

Анализ неверных гипотез: память и кэширование

В процессе обсуждения разработчики выдвигают несколько предположений, которые на первый взгляд кажутся логичными, но при детальном анализе оказываются неверными.

Гипотеза 1: Время тратится на динамическое выделение памяти под массив.
Предполагается, что при достижении определенного лимита элементов платформа 1С приостанавливает выполнение кода для перевыделения памяти под массив в оперативной памяти (реаллокация), и именно этот процесс занимает 15 мс. Это легко опровергнуть. Если мы заранее выделим память под массив, инициализировав его фиксированным размером:


Массив = Новый Массив(10000);
Для А = 0 По 9999 Цикл
    Массив[А] = ТекущаяУниверсальнаяДатаВМиллисекундах();
КонецЦикла;

Мы увидим, что характерные скачки по 15 миллисекунд все равно сохраняются. Выделение памяти в современных менеджерских кучах происходит значительно быстрее, и оно не может приводить к столь стабильным и фиксированным задержкам.

Гипотеза 2: Кэширование переменной самой платформой 1С.
Предполагается, что среда исполнения 1С оптимизирует вызовы и обновляет значение встроенной функции раз в 15 мс. Это также неверно. Платформа 1С не занимается искусственным кэшированием системного времени. При вызове функции ТекущаяУниверсальнаяДатаВМиллисекундах() происходит прямое обращение к API операционной системы.

Реальная причина: Системный таймер Windows (Timer Resolution)

Истинная причина кроется в архитектуре планировщика задач операционной системы Windows. Под капотом платформа 1С обращается к системной функции Windows API (обычно это GetSystemTimeAsFileTime).

Разберем этот механизм по шагам:

  1. Аппаратный таймер и прерывания: Операционная система Windows не отслеживает время непрерывно в наносекундном режиме для всех процессов ради экономии ресурсов процессора. Вместо этого системные часы обновляются циклически по прерываниям аппаратного таймера.
  2. Интервал по умолчанию: По умолчанию шаг системного таймера (Timer Resolution) в Windows составляет 15.625 миллисекунды (что соответствует частоте обновлений около 64 Гц).
  3. Поведение в цикле 1С: Так как встроенный язык 1С на современном процессоре выполняет простые операции добавления элементов очень быстро, за один «тик» системного таймера (15.6 мс) цикл успевает выполниться сотни раз. Все это время операционная система возвращает 1С одно и то же «замороженное» значение времени. Как только происходит очередное прерывание таймера, значение времени мгновенно обновляется («прыгает») на 15–16 мс.

Почему проблема «мистически» исчезает при повторных тестах?

В ходе обсуждения авторы замечают странность: иногда при повторных тестах эти скачки по 15 мс внезапно пропадают, и функция начинает выдавать плавные значения с точностью до 1 миллисекунды. Это объясняется глобальным состоянием Windows Timer Resolution:

Практические способы увеличения точности таймера в 1С

Если для вашей бизнес-логики или алгоритмов логирования критически важна стабильная точность времени до 1 миллисекунды и менее, стандартная функция ТекущаяУниверсальнаяДатаВМиллисекундах() в среде Windows без дополнительной настройки не гарантирует стабильного результата. Рассмотрим, как решить эту проблему.

Вариант 1. Использование Windows API (timeBeginPeriod)

Мы можем принудительно установить разрешение системного таймера Windows в 1 миллисекунду на время работы нашей сессии 1С. Для этого необходимо вызвать функцию timeBeginPeriod из системной библиотеки winmm.dll. Сделать это можно с помощью внешней компоненты или через технологию динамического вызова функций DLL (если позволяет окружение).

Примерный алгоритм работы выглядит следующим образом:

  1. При старте системы (или перед началом критических замеров) вызывается функция timeBeginPeriod(1). Это переводит системный таймер всей ОС в режим повышенной точности (1 мс).
  2. Выполняются необходимые высокоточные замеры средствами встроенного языка 1С.
  3. При завершении работы вызывается парная функция timeEndPeriod(1) для возврата системы к энергосберегающему режиму по умолчанию.

Вариант 2. Использование счетчиков высокого разрешения (QueryPerformanceCounter)

Если вам требуется микросекундная точность для замера производительности участков кода (профайлинг), стандартный таймер времени не подойдет в принципе. В операционных системах Windows для этого реализован высокоточный счетчик времени QueryPerformanceCounter (QPC), который завязан на аппаратные такты процессора.

Для интеграции с 1С пишется простая внешняя компонента (например, на C++ или C#), которая предоставляет методы получения текущего значения этого счетчика. Схема работы компоненты на C# выглядит так:


// Пример логики внешней компоненты на C# для получения точного времени
long start = System.Diagnostics.Stopwatch.GetTimestamp();
// Выполнение измеряемого кода в 1С
long end = System.Diagnostics.Stopwatch.GetTimestamp();
double elapsedMicroseconds = (double)(end - start) * 1000000 / System.Diagnostics.Stopwatch.Frequency;

Этот подход полностью независим от настроек системного таймера Windows и дает чистую точность вплоть до микросекунд.

Вариант 3. Особенности работы под управлением Linux

Стоит отметить, что если серверная или клиентская часть 1С работает под управлением ОС Linux, ситуация обстоит иначе. В Linux планировщик задач и системные таймеры обычно работают с гораздо более высокой базовой частотой обновления (зависит от конфигурации ядра, параметра HZ и поддержки режима Tickless Kernel), поэтому скачки времени там изначально менее выражены и обычно составляют не более 1–2 миллисекунд без дополнительных манипуляций.

Важное предупреждение: Ошибка создания собственного ГСЧ на основе времени

Один из участников обсуждения упомянул, что планирует использовать миллисекунды для создания собственного Генератора Случайных Чисел (ГСЧ). Это классическая архитектурная ошибка, которой следует избегать:

Рассмотрим правильный пример инициализации и использования встроенного ГСЧ:


// Инициализация ГСЧ без параметров задействует аппаратную энтропию операционной системы
ГСЧ = Новый ГенераторСлучайныхЧисел();
Для Итератор = 1 По 10 Цикл
    СлучайноеЧисло = ГСЧ.СлучайноеЧисло(1, 1000);
    Сообщить("Случайное число: " + СлучайноеЧисло);
КонецЦикла;

Этот объект инициализируется на основе криптографических провайдеров безопасности операционной системы (CryptoAPI в Windows или устройства /dev/urandom в Linux), что гарантирует высокое качество энтропии и исключает дублирование или предсказуемость результатов, с которыми вы неизбежно столкнетесь при попытке написать собственный велосипед на базе системного времени.

← На главную