Pull to refresh

Comments 54

А если появился новый таймер, как правильно в таком случае сделать останов текущего таймера и перерасчет? Или какое применение у такого таймера?

Я постарался урезать код до минимума, как показывает практика читать длинные простыни не слишком интересно.
Для этого есть alertable wait states, при добавлении нового таймера можно будить поток. Плюс, можно ограничить максимальную длительность сна - даже если всегда спать 0.5мс это не сильно убьет нагрузку на процессор. Ну или ваш вариант :)

А будет ли работать такой "таймер":

1) открываем файл COM порт на скорости передачи 19200bps в режиме FILE_FLAG_OVERLAPPED

2) записываем в файл девайса порта 2 байта

3) ждем completition Event из структуры OVERLAPPED переданной в WriteFile(..)

При скорости 19200 один бит передается за 0,000052083 секунды, но там байт и еще старт бит и стоп бит, итого 2 байта передается 0,00052083*2=0,00104166 секунды.

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

Далеко не у всех нынче есть COM порт - у меня нет :)
Но, кстати говоря, есть у меня один pet проект (даталоггер для блока управления двигателем) где используется COM через USB - когда в следующий раз буду ковыряться в нём с осцилографом то попробую.

Не уверен, но возможно документировано это делает timeBeginPeriod(1).

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

Смотреть на утилизацию процессора с при увеличении скорости системного таймера глупо. Возьмите ноут, выкиньте всё лишнее из процессов и сравните разряд батареи через N часов аптайма. Я ставлю на то, что на 20-30% больше энергии будет жрать, чем при дефолтных настройках таймера.

У меня дефолтная настройка таймера именно 0.5мс, спасибо WPF, спасибо хрому и (в моем случае) спасибо софту который мониторит напряжения на процессоре.

Номинально начиная с Win10 2004 это должно влиять гораздо меньше но конкретно в нашем продукте работа от батарей не рассматривается как класс, поэтому тему я не исследовал.

2000 тиков в секунду? Жуть какая.

cat /boot/config-5.15.0-18-generic |grep HZ
CONFIG_NO_HZ_COMMON=y
CONFIG_NO_HZ_IDLE=y
CONFIG_NO_HZ=y
CONFIG_HZ_250=y
CONFIG_HZ=250

Начиная с Windows 8, ядро стало tick-less, поэтому прямой связи тут нет.

А, они тоже на NO_HZ перешли? Молодцы.

Функция unsafe потому что автор кода - ленивая жопа 
Перед броском гнилым помидором подумайте: хотелось бы вам выделять память вручную?

Надо было банально прочитать инструкцию. Для таких случаев маршалинга используется ref long

Для тех кто подумал "neat" пора переписывать всё с поинтеров на "ref X" хочу упомянуть, что это приводит к маршалингу т.е. будет сгенерена "ненужная" прокладка через которую будет вызван нативный метод. Не все сценарии требуют лучшего пефоманса, там 'ref' подходят.

Да, виноват, в рамках примера это наверное было бы лучше чем С-стайл.

Хм. Прикольно, но я затрудняюсь придумать применение.
Мне несколько лет назад требовалось таймить запросы к USB устройству (адаптер для энкодера), так там время выполнения запроса гуляло так, что в итоге пришлось плюнуть и вынести отслеживание энкодера и сбор данных на ардуинку. Боюсь, с другим вводом-выводом будет примерно то же самое.

Разве что какой-то специфический race condition ловить в чужом коде?

Ещё несколько лет назад писали тут на Хабре, что Chrome меняет разрешение системного таймера на 1мс, и после закрытия не меняет назад. Видимо для ускорения таймаутов в JS, анимаций и т.п.

Конкретно 1мс таймер? Это код который не имеет применения... Или имеет :)
У нас в коде используются промежутки 10 мс и более, проблема в том что с приемлемой точностью 10мс отмерить тоже нечем, мультимедиа таймеры точностью не отличились. Т.е. это в первую очередь точный таймер а не быстрый таймер. Просто так случилось что его точности хватает и для промежутков в 1мс и менее, а это уже отличный повод похвалиться успехами, не так ли?

А asm команда rdtsc не подойдет? В ЯП Delphi-7 использовал таким образом:


type
  TCPUTime = record
              case Boolean of
              true: (w1,w2 : dword);
              false: (t: int64);
              end;
var
  CPUTime : TCPUTime;

procedure getCPUTime;
asm
      rdtsc
      mov CPUTime.w1,eax
      mov CPUTime.w2,edx
end;

Команда не спит, команда просто читает время. В случае с Windows такое доступно при помощи QueryPerformanceCounter

Извините, не понял, чем мое решение хуже Вашего? (В случае с Windows asm доступен).

Тем что это решение не решает проблему вызова функции раз в 1 мс а просто получает текущее значение счетчика циклов процессора, поведение которого зависит от конкретной модели процессора.

А вот задача "измерить точное время" это совершенно иная история и по состоянию на сегодня она уже успешно решается стандартными библиотеками - в C# это Stopwatch.StartNew(), в случае с C++ это std::chrono::steady_clock::now(), а для C (и других языков) даже без ASM в Win32 доступен QueryPerformanceCounter

UFO just landed and posted this here

Это в статье по ссылке в начале есть - вариант "while цикл", и, собственно, вся сложность и состоит в том чтобы поток не занимал ядро на 100%

UFO just landed and posted this here
UFO just landed and posted this here

Собственно проблема в том что пока ядро исполняет нопы - оно не исполняет ничего полезного (а могло бы), поэтому уступить квант времени - априори оптимальнее.

Далее, если уж писать на плюсах то всю работу с rdtsc можно заменить на вызовы std::chrono::steady_clock::now() (номинально std::chrono::high_resolution_clock::now(), но учитывая "нюансы с реализациями" steady_clock будет более переносимым решением).

Ну и, наверное, стоит уточнить что вся суть моей статьи - это как сделать таймер на 1мс не скатываясь до цикла с нопами. Цикл с нопами работает, и он у меня в коде тоже есть, но цель - крутить нопы только в случае крайней необходимости.

UFO just landed and posted this here

ну когда у нас ядра растут как на дрожжах это уже не является проблемой

В этом и вся суть - то что для вас не является проблемой, для нас является. Сценарии есть разные, я же не заставляю везде использовать предложенный таймер :)
К примеру, наш продукт состоит кучи процессов-сервисов большинство из которых содержит таймер, обычно по несколько процессов на машине, в вырожденных случаях 10 и более. Ну а машины - не наши, есть и домашние пользователи с 4 ядрами и даже двухьядерники попадаются на вспомогательных машинах.
И как раз недавно в таймере был баг который приводил к тому что он крутил очень много нопов - в итоге машину только с кнопки можно было перезагрузить, глухое зависание
Предложенный таймер прекрасно справлялся с задачей на том же железе пока я туда не влез и не поломал, ни до ни после жалоб не было.

ну использовать недокументироыванные функции это тоже не решение. Завтра выйдет обновление и досвидос.

Тут стоит понимать что "недокументированные функции" тоже бывают разными. NtDelayExecution существует с доисторических времен (не удивлюсь если аналог был ещё на OS/2), это KeDelayExecutionThread, другие две функции добавлены в Windows NT 3.5 в 94м году и уже тоже документированы на уровне ядра: ExQueryTimerResolution и ExSetTimerResolution. В целом, я готов поспорить что они никуда не собираются в ближайшем будущем.

поэтому правильное решение все таки только через драйвер идти.

Да, это так. Зря что у меня нету сертификата которым можно этот драйвер подписать :)

и знаю что 10 тактов занимает вызов

Будем честны - не всё так просто

вернее знаю и мне не нравится что там под капотом

В случае с Windows и MSVC это будет QueryPerformanceCounter, который, в случае со свежими версиями Windows и процессором где есть Invariant TSC, будет использовать rdtsc (если хочется проверить лично - смотреть внутрь функции RtlQueryPerformanceCounter).
В случае с Linux логично предположить что будет использоваться clock_gettime с параметром CLOCK_MONOTONIC который, совершенно внезапно, точно так же использует rdtsc в случае если процессор поддерживает Invariant TSC.

UFO just landed and posted this here

Как написано в статье Performance measurements with RDTSC:


You should not convert your results to seconds. Report them in clock cycles.

From user's point of view, execution time in seconds makes more sense than clock cycles. But remember that:

time_in_seconds = number_of_clock_cycles / frequency

Отсюда тривиальное решение:
Задаем time_in_seconds = 0.001
и считаем сколько number_of_clock_cycles должно пройти,
как пройдет, так ok


А переводить каждый раз во время не надо.

для десктопа может и пойдёт. Как костыль.

"В целом" есть по крайней мере две проблемы:

1) архитектура бывает не только x86. Даже у windows.

2) Неспящий тред не всегда бесплатен. Если вы в облаке и платите за процессорное время - такой тред может быть ВЕСЬМА не бесплатен.

Согласен, как минимум миди кривое на винде, по сравнению с железными секвенсорами, стробит куда хочет то вперед, то назад, правда далеко не для всякой музыки нужен хороший миди тайминг

В случае с миди решающее влияние оказывают задержки при передаче управления в 16-разрядный код и обратно. Миди подсистема присутствует в современных виндах только потому, что она осталась со времён 16-разрядных винд.

А почему вы считаете, что подсистема MIDI должна отсутствовать в Windows или любой другой ОС? Она присутствует, потому что MIDI – протокол, активно используемый в музыкальном мире. Введённый в начале 80-х, он живее всех живых. Более того, 2 года назад был выкачен MIDI 2.0 (который крайне медленно набирает обороты, но это другая история).

MIDI API есть в Windows, в macOS, и очень хорошо, что он есть. В Windows есть новый API, но у него свои болячки.

Да, использование конвертера юсб-миди - это работает, но это не про хороший тайминг миди. Правда я не совсем понимаю, имеет ли отношение (и может ли иметь) тема статьи к миди?

Ну вот первоначальная статья про 1мс тайминг посвящена именно проблемам с MIDI - насколько я понял, в Windows при выводе MIDI нет нормальной буферизации и нужно софтварно реализовать тайминг.

Насколько я знаю, MIDI в винде был нужен, чтобы воспроизводить системные звуки. А сейчас он ни к чему, есть формат wav. Но, по непонятным мне причинам, MIDI подсистема осталась.

Вы путаете MIDI и WAV. MIDI никак не связан с продуцированием звука, это протокол общения между, например, компьютером и синтезатором. MIDI активно используется, как средство передачи партий в музыкальной среде. Например, можно записать партию ударных в MIDI, и вывести этот MIDI разными сэмплерами, получая уже как раз WAV. К системным звукам Windows протокол отношения не имеет. Сама подсистема в ОС нужна, и хорошо, что она существует.

Пробовали, у них большой разброс - иногда таймер опаздывает, иногда спешит, иногда вообще не работает. Собственно мне 1мс интервал не нужен (просто оказалось что мой таймер так может, в продакшне у нас не применяется), таймер был написан именно из-за проблем с мультимедиа таймером.

Лет 20 назад делал эксперименты и писал vxd драйвера. В частности программировал таймер и обрабатывал прерывания. Так вот, меняя делитель, настроить частоту проблемы нет. Для контроля менял состояние выходов LPT порта и проверял при помощи осцилографа. Частоту держало, но периодически (предполагаю при свопе на диск) всё плыло. Реже на такую же проблему наблюдал даже на QNX 4.25, если на десктопном варианте с Фотоном. Хотя система реального времени. На одноплатниках с чистым QNX проблем вроде не было.

Поэтому стоит разделять:

  • возможности самого таймера,

  • летенси обработки прерываний таймера и летенси добавляемый OS,

  • доступ к настройкам таймера в пользовательском API (на уровне драйверов он ограничен лишь самим аппаратным таймером)

Такую практику сейчас найти невозможно, вам здорово повезло

В целом это решение на жесткое реальное время не претендует - в случае повышенной нагрузки на систему оно уплывёт. У меня есть вариант посложнее с комленсациями но всё-таки Windows - не RTOS, а C# так и подавно, приходит Garbage Collection и можно гасить свет.

Вы продолжительность промежутка для контроля своего метода меряете тем же инструментом, которым этот промежуток формируете. Так не пойдёт. Просим осциллограф.

Пришёл написать этот комментарий. Да, гладкость графиков - это показатель упорядоченности квантов исполнения, а не их реальной равномерности в физическом времени. Нужно замерять внешним инсртументом, и очень может оказаться, что ограничили точность таймеров разработчики винды вовсе не из жадности (и не из жадности всякие конторы торгуют реалтаймовыми ядрами осей) :).

Разница в том что реалтаймовое ядро дает гарантии а тут... "как получится".

Измерить извне я могу попробовать но мне банально некуда воткнуть щуп осцилографа - нужна железка, быстрая, без буфера, без регистрации и без смс. Разве что взять USB-COM адаптер и попробовать на нём сгенерировать 500Гц меандр и посмотреть что с этого получится

Ну да, хотя бы меандром 500 Гц. Я практически уверен, что там будут пропущенные периоды в изрядном количестве.

Собственно обновил статью с измерением при помощи осциллографа

Замечание верное, но, откровенно говоря, для промежутков в 1мс (или даже 0.1мс) у современных машин на Windows средства измерения с огромнейшим избытком - измерить (и проконтролировать) можно, а вот уснуть на малый промежуток - нечем. Собственно, к решению задачи изначально подход с предположением что хардварный счетчик который используется ОС имеет запас в несколько порядков, конкретно на моей машине Windows репортит точность в 100ns и дальше исходим из доверия к этому счетчику.

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

Не совсем понял, будет ли это рабочим решением в win 7 или только сильно позднее?

У нас вроде как работает уже пару лет, у клиентов есть Win7 машины. Но конкретно таймер в 1мс без гарантий - у нас этот же код крутит 10мс таймер повышенной точности.

10 и 15 не столь большая разница все-таки, а вот если выставить 1мс локально, а окажется, что оно глобально утекло…
Sign up to leave a comment.

Articles