Частота таймера javascript что это
Перейти к содержимому

Частота таймера javascript что это

  • автор:

Асинхронное выполнение: setImmediate

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Функция, отложенная через setTimeout(..0) выполнится не ранее следующего «тика» таймера, минимальная частота которого может составлять от 4 до 1000 мс. И, конечно же, это произойдёт после того, как все текущие изменения будут перерисованы.

Но нужна ли нам эта дополнительная задержка? Как правило, используя setTimeout(func, 0) , мы хотим перенести выполнение func на «ближайшее время после текущего кода», и какая-то дополнительная задержка нам не нужна. Если бы была нужна – мы бы её указали вторым аргументом вместо 0 .

Метод setImmediate(func)

Для того, чтобы поставить функцию в очередь на выполнение без задержки, в Microsoft предложили метод setImmediate(func). Он реализован в IE10+ и на платформе Node.JS.

У setImmediate единственный аргумент – это функция, выполнение которой нужно запланировать.

В других браузерах setImmediate нет, но его можно эмулировать, используя, к примеру, метод postMessage, предназначенный для пересылки сообщений от одного окна другому. Детали работы с postMessage вы найдёте в статье Общение окон с разных доменов: postMessage. Желательно читать её после освоения темы «События».

Полифил для setImmediate через postMessage :

if (!window.setImmediate) window.setImmediate = (function() < var head = < >, tail = head; // очередь вызовов, 1-связный список var // уникальный идентификатор function onmessage(e) < if(e.data != ID) return; // не наше сообщение head = head.next; var func = head.func; delete head.func; func(); >if(window.addEventListener) < // IE9+, другие браузеры window.addEventListener('message', onmessage); >else < // IE8 window.attachEvent( 'onmessage', onmessage ); >return function(func) < tail = tail.next = < func: func >; window.postMessage(ID, "*"); >; >());

Есть и более сложные эмуляции, включая MessageChannel для работы с Web Workers и хитрый метод для поддержки IE8-: https://github.com/NobleJS/setImmediate. Все они по существу являются «хаками», направленными на то, чтобы обеспечить поддержку setImmediate в тех браузерах, где его нет.

Тест производительности

Чтобы сравнить реальную частоту срабатывания – измерим время на 100 последовательных вызовов при setTimeout(..0) по сравнению с setImmediate :

Почему начинает тормозить таймер?

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

 var fn = function() < function animation_zone() < tgl++; if (tgl == 1) < $("div").fadeOut("0", function() < $("div").fadeOut("1000", function() < $("div").fadeIn("1000"); >); >); $("div").fadeOut("1000", function() < $("div").fadeIn("1000"); setTimeout(function() < $("div").fadeIn("1000", function() <>) >, 7000) >); > if (tgl == 2) < $("div").stop().fadeOut("0", function() < $("div").fadeOut("1000", function() < $("div").fadeIn("1000"); >); >); $("div").fadeOut("1000", function() < $("div").fadeIn("1000"); >); setTimeout(function() < $("div").fadeIn("1000"); >, 7000) tgl = 0; > > animation_zone(); setTimeout(arguments.callee, 14300); > setTimeout(fn, 14300);

Частота таймера javascript что это

Таймеры в ThingWorx — это тип сущностей, которые инициируют события в определенном интервале времени.

Опции общей информации

(Необязательно) Описание таймера.

(Необязательно) Группа проектов для использования таймером.

(Необязательно) Теги.

Базовый шаблон вещи

(Требуется) Для сущности таймера используется базовый шаблон вещи Таймер .

Реализованные структуры

(Необязательно) Профили вещей для реализации.

Поток значений

(Необязательно) Поток значений для добавления в сущность таймера.

Пользователь для запуска

(Требуется) Пользователь ThingWorx, который может использоваться таймером.

Не рекомендуется назначать участника группы администраторов в качестве Пользователя для запуска .

Частота обновления

(Требуется) Частота срабатывания таймера (в миллисекундах).

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

Главный мэшап

(Необязательно) Главный мэшап для связывания с таймером.

Документация

(Необязательно) Информация о таймере.

Системный таймер в Windows: большое изменение

Поведение планировщика Windows значительно изменилось в Windows 10 2004 без каких-либо предупреждений и изменения документации. Вероятно, это поломает несколько приложений. Такое происходит не первый раз, но эта перемена посерьёзнее.

Если вкратце, то вызовы timeBeginPeriod из одного процесса теперь влияют на другие процессы меньше, чем раньше, хотя эффект ещё присутствует.

Думаю, что новое поведение — это по сути улучшение, но оно странное, и заслуживает того, чтобы быть задокументированным. Честно предупреждаю — у меня только результаты собственных экспериментов, поэтому могу только догадываться о целях и каких-то побочных эффектах этого изменения. Если какие-либо из моих выводов неверны, пожалуйста, дайте знать.

Прерывания таймера и смысл их существования

Во-первых, немного контекста о дизайне операционных систем. Желательно, чтобы программа могла засыпать, а позже — просыпаться. На самом деле это не следует делать очень часто — потоки обычно ждут событий, а не таймеров, — но иногда необходимо. Итак, в Windows есть функция Sleep — передайте ей желаемую продолжительность сна в миллисекундах, и она разбудит процесс:

Стоит подумать о том, как это реализуется. В идеале при вызове Sleep(1) процессор переходит в спящий режим. Но как операционная система разбудит поток, если процессор спит? Ответ — аппаратные прерывания. ОС программирует микросхему — аппаратный таймер, который затем запускает прерывание, которое пробуждает процессор, и ОС затем запускает ваш поток.

Функции WaitForSingleObject и WaitForMultipleObjects также имеют значения таймаута, и эти таймауты реализуются с использованием того же механизма.

Если много потоков ждут таймеров, то ОС может запрограммировать аппаратный таймер на индивидуальное время для каждого потока, но это обычно приводит к тому, что потоки просыпаются в случайное время, а процессор так нормально и не засыпает. Энергоэффективность CPU сильно зависит от времени его сна (нормальное время от 8 мс), и случайные пробуждения тому не способствуют. Если несколько потоков синхронизируют или объединяют свои ожидания таймера, то система становится более энергоэффективной.

Существует множество способов объединения пробуждений, но основной механизм в Windows — глобальное прерывание таймера, тикающего с постоянной скоростью. Когда поток вызывает Sleep(n), то ОС запланирует запуск потока сразу после первого прерывания таймера. Это означает, что поток может в конечном итоге проснуться немного позже, но Windows — это не ОС реального времени, она вообще не гарантирует определённое время пробуждения (в это время ядра процессора могут быть заняты), поэтому вполне нормально проснуться чуть позже.

Интервал между прерываниями таймера зависит от версии Windows и железа, но на всех моих машинах он по умолчанию составлял 15,625 мс (1000 мс/64). Это означает, что если вызвать Sleep(1) в какое-то случайное время, то процесс будет разбужен где-то между 1,0 мс и 16,625 мс в будущем, когда сработает следующее прерывание глобального таймера (или через одно, если это сработало слишком рано).

Короче говоря, природа задержек таймера такова, что (если только не используется активное ожидание процессора, а его, пожалуйста, не используйте) ОС может пробуждать потоки только в определённое время с помощью прерываний таймера, а Windows использует регулярные прерывания.

Некоторым программам не подходит такой большой разброс в задержках ожидания (WPF, SQL Server, Quartz, PowerDirector, Chrome, Go Runtime, многие игры и т. д.). К счастью, они могут решить проблему с помощью функции timeBeginPeriod, которая позволяет программе запросить меньший интервал. Есть также функция NtSetTimerResolution, которая позволяет устанавливать интервал меньше миллисекунды, но она редко используется и никогда не требуется, поэтому не буду больше её упоминать.

Десятилетия безумия

Вот сумасшедшая вещь: timeBeginPeriod может вызвать любая программа, и она изменяет интервал прерывания таймера, при этом прерывание таймера — это глобальный ресурс.

Представим, что процесс А находится в цикле с вызовом Sleep(1). Это неправильно, но это так, и по умолчанию он просыпается каждые 15,625 мс, или 64 раза в секунду. Затем появляется процесс B и вызывает timeBeginPeriod(2). Это заставляет таймер срабатывать чаще, и внезапно процесс А просыпается 500 раз в секунду вместо 64-х раз в секунду. Это безумие! Но именно так всегда работала Windows.

В этот момент, если бы появился процесс C и вызвал timeBeginPeriod(4), это ничего бы не изменило — процесс A продолжал бы просыпаться 500 раз в секунду. В такой ситуации правила устанавливает не последний вызов, а вызов с минимальным интервалом.

Таким образом, вызов timeBeginPeriod от любой работающей программы может установить глобальный интервал прерывания таймера. Если эта программа завершает работу или вызывает timeEndPeriod, то вступает в силу новый минимум. Если одна программа вызывает timeBeginPeriod(1), то теперь это интервал прерывания таймера для всей системы. Если одна программа вызывает timeBeginPeriod(1), а другая timeBeginPeriod(4), то всеобщим законом становится интервал прерывания таймера в одну миллисекунду.

Это имеет значение, потому что высокая частота прерываний таймера — и связанная с ней высокая частота планирования потоков — может впустую расходовать значительную мощность CPU, как обсуждалось здесь.

Одним из приложений, которому необходимо планирование на основе таймера, является веб-браузер. В стандарте JavaScript есть функция setTimeout, которая просит браузер вызвать функцию JavaScript через несколько миллисекунд. Для реализации этой и других функций Chromium использует таймеры (в основном WaitForSingleObject с таймаутами, а не Sleep). Это часто требует повышенной частоты прерываний таймера. Чтобы это не слишком сказывалось на времени автономной работы, Chromium недавно модифицировали таким образом, чтобы при работе от батареи частота прерываний таймера не превышала 125 Гц (интервал 8 мс).

timeGetTime

Функция timeGetTime (не путать с GetTickCount) возвращает текущее время, обновлённое прерыванием таймера. Процессоры исторически не очень хороши в ведении точного времени (их часы специально колеблются, чтобы не служить FM-передатчиками, и по другим причинам), поэтому для поддержания точного времени CPU часто полагаются на отдельные генераторы тактовых импульсов. Чтение с этих чипов стоит дорого, поэтому Windows поддерживает 64-битный счётчик времени в миллисекундах, обновляемый прерыванием таймера. Этот таймер хранится в общей памяти, поэтому любой процесс может дёшево считывать оттуда текущее время, не обращаясь к генератору тактовых импульсов. timeGetTime вызывает ReadInterruptTick, который по сути просто считывает этот 64-битный счётчик. Все просто!

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

Новая недокументированная реальность

С выпуском Windows 10 2004 (апрель 2020 года) некоторые из этих механизмов слегка изменились, но очень запутанным образом. Сначала появились сообщения, что timeBeginPeriod больше не работает. На самом деле всё оказалось куда сложнее.

Первые эксперименты дали смешанные результаты. Когда я запустил программу с вызовом timeBeginPeriod(2), то clockres показал интервал таймера 2,0 мс, но отдельная тестовая программа с циклом Sleep(1) просыпалась около 64 раз в секунду вместо 500 раз, как в предыдущих версиях Windows.

Научный эксперимент

Тогда я написал пару программ для изучения поведения системы. Одна программа (change_interval.cpp) просто сидит в цикле, вызывая timeBeginPeriod с интервалами от 1 до 15 мс. Она удерживает каждый интервал в течение четырёх секунд, а затем переходит к следующему, и так по кругу. Пятнадцать строк кода. Легко.

Другая программа (measure_interval.cpp) запускает несколько тестов для проверки, как её поведение изменяется при изменении change_interval.cpp. Программа отслеживает три параметра.

  1. Она спрашивает ОС, каково текущее разрешение глобального таймера, используя NtQueryTimerResolution.
  2. Она измеряет точность timeGetTime, вызывая его в цикле до тех пор, пока возвращаемое значение не изменится — и отслеживая величину, на которую оно изменилось.
  3. Она измеряет задержку Sleep(1), вызывая его в цикле в течение секунды и подсчитывая количество вызовов. Средняя задержка является просто обратной величиной числа итераций.

Это означает, что timeBeginPeriod по-прежнему устанавливает интервал глобального таймера во всех версиях Windows. Из результатов timeGetTime() можно сказать, что прерывание срабатывает с такой скоростью по крайней мере на одном ядре процессора, и время обновляется. Обратите также внимание, что 2.0 в первой строке для 1909 года тоже было 2.0 в Windows XP, затем 1.0 в Windows 7/8, а затем вроде опять вернулось к 2.0?

Однако поведение планировщика резко меняется в Windows 10 2004. Ранее задержка для Sleep(1) в любом процессе просто равнялась интервалу прерывания таймера, за исключением timeBeginPeriod(1), давая такой график:

В Windows 10 2004 соотношение между timeBeginPeriod и задержкой сна в другом процессе (который не вызывал timeBeginPeriod) выглядит странно:

Точная форма левой части графика неясна, но она определённо уходит в противоположную сторону от предыдущего!

Последствия

Как было указано в обсуждении reddit и hacker-news, вероятно, левая половина графика представляет собой попытку максимально точно имитировать «нормальную» задержку, учитывая доступную точность глобального прерывания таймера. То есть с интервалом прерывания 6 миллисекунд задержка происходит примерно на 12 мс (два цикла), а с интервалом прерывания 7 миллисекунд — примерно на 14 мс (два цикла). Однако измерение фактических задержек показывает, что реальность ещё более запутанна. При прерывании таймера, установленном на 7 мс, задержка Sleep(1) в 14 мс даже не самый распространённый результат:

Некоторые читатели могут обвинить случайный шум в системе, но когда частота прерывания таймера 9 мс и выше, шум равен нулю, так что это не может быть объяснением. Попробуйте сами запустить обновлённый код. Особенно противоречивыми кажутся интервалы прерывания таймера от 4 мс до 8 мс. Вероятно, измерения интервалов следует выполнять с помощью QueryPerformanceCounter, поскольку на текущий код беспорядочно влияют изменение правил планирования и изменение точности таймера.

Всё это очень странно, и я не понимаю ни логики, ни реализации. Может, это и ошибка, но я в этом сомневаюсь. Думаю, что за этим стоит сложная логика обратной совместимости. Но самый эффективный способ избежать проблем совместимости — это документировать изменения, желательно заранее, а здесь правки сделаны без какого-либо уведомления.

Это не повлияет на большинство программ. Если процесс хочет более быстрое прерывание таймера, то он сам должен вызвать timeBeginPeriod. Тем не менее, могут возникнуть следующие проблемы:

  • Программа может случайно предположить, что у Sleep(1) и timeGetTime одинаковое разрешение, а это теперь не так. Хотя, такое предположение кажется маловероятным.
  • Программа может зависеть от маленького разрешения таймера, которого не получает. Было несколько сообщений о такой проблеме в некоторых играх — есть инструмент под названием Windows System Timer Tool и ещё один под названием TimerResolution 1.2. Они «исправляют» эти игры, повышая частоту прерываний таймера. Видимо, эти исправления больше не будут работать или будут работать не так хорошо. Возможно, это заставит разработчиков игр выпустить правильные патчи, но до тех пор изменение вызывает проблемы обратной совместимости.
  • В многопроцессной программе главная управляющая программа может повысить частоту прерываний таймера, а затем ожидать, что это повлияет на планирование дочерних процессов. Раньше это было разумное предположение, но теперь оно не работает. Именно так я сам узнал об этой проблеме. Продукт, о котором идёт речь, теперь вызывает timeBeginPeriod в каждом процессе, так что всё в порядке, но несколько месяцев программное обеспечение плохо работало по непонятной причине.

Жертва

Тестовая программа change_interval.cpp работает только в том случае, если никто не запрашивает более высокую частоту прерываний таймера. Поскольку и Chrome, и Visual Studio имеют привычку делать это, мне пришлось проделать большую часть моих экспериментов без доступа в интернет и программируя в блокноте. Кто-то предложил Emacs, но ввязываться в эту дискуссию выше моих сил.

  • Системное администрирование
  • Разработка под Windows
  • Игры и игровые консоли

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *