LINUX.ORG.RU

C++: как повысить точность задержек?

 , , , ,


0

2

Лагают функции задержки, не важно какие - usleep, this_thread::sleep_for или цикл с проверкой времени с nop или yield внутри. Отставание на 4%, игра на экране кажется медленной, но без задержек или с vsync работает быстрее, короч не могу 16.66 мс нормально выждать. На винде такой проблемы нету, HPET видит как включённый. Если на линуксе запустить с высокими приоритетами в nice и chrt, то задержка будет точной. Нет другого способа улучшить точность задержки? В perf видно тормоза в vdso_clock_gettime



Последнее исправление: HPW-dev (всего исправлений: 2)

Не используй задержки, в SDL указано что такой точности не будет на всех платформах.

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

У меня монитор 277 гц, представь какая точность задержки тебе нужна.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 4)

Если на линуксе запустить с высокими приоритетами в nice и chrt, то задержка будет точной. Нет другого способа улучшить точность задержки?

Мне кажется что эти самые высокие приоритеты и говорят ядру о том, что приложению нужны точные задержки. Ты хочешь найти нестандартный способ сообщить об этом ядру? Зачем?

короч не могу 16.66 мс нормально выждать.

А вообще у тебя какой-то странный способ организации таймингов игры. Это делается не так. Используй clock_gettime(CLOCK_MONOTONIC) и по нему считай на каком месте тебе надо проснуться. Ну то есть, допустим на старте он тебе выдал X нс, а ты хочешь кадры длиной в 16660000нс. Значит, считаешь X+N*16660000, сравниваешь с текущим значением clock_gettime() и делаешь sleep на оставшееся время.

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

firkax ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Частота монитора к этим задержкам вообще ни при чём. Если надо синхронизироваться с монитором то это vsync, а он хочет просто тактовый генератор для игровой механики. Перерисовывать картинку каждый 1/277 с это явная глупость, я бы раз в 5 кадров это делал.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Не душни, дай человеку похвастаться новым монитором.

ox55ff ★★★★★
()
Ответ на: комментарий от firkax

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

Приоритеты - вроде как для этого самого они и сделаны.

Если считаешь, что умнее планировщика, то можно в дополнение к приоритетам попробовать affinity, чтобы процесс не таскали с ядра на ядро.

blex ★★★
()

Согласен с комментатором предлагающим не юзать задержки.

Но если решать задачу в вакууме - функционал chrt можно вызывать для отдельных потоков процесса с помощью функций типа pthread_setschedparam.

faq2
()
Ответ на: комментарий от firkax

Тоже можно, но он в итоге выйдет запутаннее чем clock_gettime().

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

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

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

Enthusiast ★★★
()
Ответ на: комментарий от Enthusiast

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

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

Это всё определяется приоритетами. Чтобы соседний процесс не мешал надо ему его понизить. Если не понизишь - он в любом случае сможет испортить тебе процовое время.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Перерисовывать картинку каждый 1/277 с это явная глупость, я бы раз в 5 кадров это делал.

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

hateyoufeel ★★★★★
()
Ответ на: комментарий от firkax

Ты предлагаешь полезную нагрузку в обработчик сигнала что ли засунуть? …

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

Это всё определяется приоритетами. Чтобы соседний процесс не мешал надо ему его понизить. Если не понизишь - он в любом случае сможет испортить тебе процовое время.

По моим замерам осциллографом отклонение таймерного прерывания составило 2 .. 3 мкс от одного срабатывания до другого на самосборном ядре «Линукса» без всяких «заплаток» реального времени.

Enthusiast ★★★
()

Можно попробовать timerfd. Он довольно точный, но есть только под линукс.

imatveev13
()
Ответ на: комментарий от Enthusiast

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

В микроконтроллерах реального времени обычно нет никаких линуксов, там прошивка которая полностью сама всем своим временем управляет.

По моим замерам осциллографом отклонение таймерного прерывания составило 2 .. 3 мкс от одного

Ты это делал при 0% idle cpu?

И не надо называть это прерыванием. Это не прерывание (сигнал от внешнего железа процу), это сигнал (от ядра процессу).

firkax ★★★★★
()

Гуглить в сторону организации стабильных tps (тебе же tps надо а не fps). Те же ребятки которые факторку написали это как-то решили.

peregrine ★★★★★
()
Ответ на: комментарий от faq2

Угу и на быстром компьютере у тебя всё с одной скоростью происходит а на медленном с другой... Не ребят, так не делают.

peregrine ★★★★★
()
Ответ на: комментарий от MOPKOBKA

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

Я не могу этого сделать, потому что delta time фиксирован и тикрейт игры 240 гц, он отвязан от фпс

монитор 277 гц

Сейчас на винде я могу соблюсти такую задержку через этот алгоритм:

using Seconds = std::chrono::duration<double, std::ratio<1, 1>>;
auto st = std::chrono::steady_clock::now();
while (std::chrono::steady_clock::now() - st < Seconds(time)) {
  std::this_thread::yield();
}

Неточность в 0.001%, а на линуксе этот код тормозит наоборот

HPW-dev
() автор топика
Ответ на: комментарий от firkax

Ты хочешь найти нестандартный способ сообщить об этом ядру?

Мне надо чтоб юзер не прописывал ничего чтобы ему игра с точными задержками работала

делаешь sleep на оставшееся время

Я знаю время кадра и сколько надо подождать, но сам слип ждёт больше чем надо

HPW-dev
() автор топика
Ответ на: комментарий от firkax

я бы раз в 5 кадров это делал

Я специально добавил чтобы юзер сам выбирал сколько хочет фпс, может ему obs или бандикам отожрёт половину и он хочет с запасом, но кастомная задержка фпс оказалась тормозной

HPW-dev
() автор топика
Ответ на: комментарий от peregrine

тебе же tps надо а не fps

не, tps фиксирован на 240 гц Надо именно лимит на фпс чтоб юзер выбрать мог и чтобы было столько, сколько он захотел, а не с лагами

HPW-dev
() автор топика
Ответ на: комментарий от firkax

Ты это делал при 0% idle cpu?

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

И не надо называть это прерыванием. Это не прерывание (сигнал от внешнего железа процу), это сигнал (от ядра процессу).

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

Enthusiast ★★★
()

Я ещё раз проверил, hpet в системе включен в current_clocksource и в grub. Не знаю в чём дело

HPW-dev
() автор топика

Отставание на 4%, игра на экране кажется медленной

В геймдеве не ориентируются на длительность таймера. Ориентируются на delta time между началом отрисовки одного кадра и началом отрисовки следующего кадра. И от dt уже пересчитывают изменения игровых объектов.

Norgat ★★★★★
()
Ответ на: комментарий от Norgat

И от dt уже пересчитывают изменения игровых объектов.

Фиксирован дт

Я знаю время апдейта и рендера, мне надо рендер с настраиваемым фпс

HPW-dev
() автор топика
Ответ на: комментарий от HPW-dev

Что значит больше чем надо?

Первый кадр условно 0мс, второй 16.66, третий 33.32, четвёртый 49.98, пятый 66.64

А у тебя как получается и почему?

firkax ★★★★★
()
Ответ на: комментарий от firkax

Что значит больше чем надо?

Захочу 60 фпс, задержка будет 16.78, а не 16.666

Захочу 360, задержка 2.88, а не 2.77

HPW-dev
() автор топика
Ответ на: комментарий от Enthusiast

К чему ты это написал? Я спросил простой вопрос - cpu idle было 0% или нет? Какие у тебя там обмены данными мне не важно.

Прерываниями в компьютерной сфере называют именно аппаратные сигналы, ты своей нестандартной терминологией только путаницу создаёшь.

firkax ★★★★★
()
Ответ на: комментарий от HPW-dev

Фиксирован дт

Я тебе еще раз говорю - так не делают. Это просто не будет переносимо с одного компа на другой. Да банальное включение/отключение браузера с твичом на втором экране может влиять на твой софт тогда.

Norgat ★★★★★
()

У меня реально чёто с системой не так, мне perf top пишет что у меня оверхед read_hpet в 17%

HPW-dev
() автор топика
Ответ на: комментарий от HPW-dev

Пофиг на задержку, какие таймстампы кадров получаются? Ещё раз: я так понял, что тебе нужны 0 16.66 33.32 49.98 66.64 для первых пяти кадров.

firkax ★★★★★
()
Ответ на: комментарий от Norgat

Да банальное включение/отключение браузера с твичом на втором экране может влиять на твой софт тогда.

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

HPW-dev
() автор топика
Ответ на: комментарий от firkax

тебе нужны 0 16.66 33.32 49.98 66.64 для первых пяти кадров.

Тут всё ровно на винде, но на лине с задержкой заметной. Без моих задержек через vsync из glfw тайминги ровные

HPW-dev
() автор топика
Ответ на: комментарий от HPW-dev

Омг, ты можешь написать какие числа получаются? Третий раз уже спрашиваю, а ты каждый раз что-то другое отвечаешь.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от HPW-dev

Это не получится реализовать через фиксацию таймеров. Такое доступно ТОЛЬКО если у тебя полный контроль над железом (с точностью до настройки обработчиков аппаратных прерываний).

Если ты делаешь игру для Linux/Windows/Mac - это просто не заработает, потому что OS не гарантирует твоему процессу, что квант процессорного времени будет тебе передан когда таймер сработает. Отсюда и возникают задержки.

Поэтому и используют dt - пусть картинка немного отличается на компах, но хотя бы можно плюс минус точно вычислять что-то.

Norgat ★★★★★
()
Ответ на: комментарий от firkax

Частота монитора к этим задержкам вообще ни при чём.

Конечно же она максимально связана. Если ты двигаешь что то с точность 60 кадров в секунду, то на мониторе 270 гц это все равно будет дергано, даже если рисуешь ты быстрее. Как сделать нормально, я написал.

Но ОП вроде про фпс, и про отрисовку. Поэтому повторюсь, представь какая точность нужна для 1/277.

Перерисовывать картинку каждый 1/277 с это явная глупость

В игры которые так не могут, на моем мониторе тормозят.

Из игр которые не могут в нормальный фпс, знаю очень мало, все они на движке Bethesda.

ox55ff, уже даже в телефонах зачастую 120 гц.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 7)
Ответ на: комментарий от HPW-dev

У тебя же получается есть доступ к циклу кадров, просто пропускай кадры? Если пользователь выставил 60 при 120 гц, то пропускай каждый второй.

MOPKOBKA ★★★★★
()
Ответ на: комментарий от MOPKOBKA

Хорошо, давай теоретически. У тебя есть конечный автомат по сути, который время от времени синхронно для разных объектов меняет состояния, скажем 20 раз в секунду. Что-то должно 20 раз в секунду меняться, а что-то скажем каждые 12000 тактов. Как решать данную задачу?

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от peregrine

Не понял про какие ты именно такты говоришь. Все что нужно, взять время между предыдущим проходом цикла, и текущим, и эту переменную потом применять везде где есть зависимость от времени, например в физике, или скорости роста растений. На быстром компьютере с 100500 проходами циклов за секунду, каждый кадр будет двигать все медленнее, чем на более медленном компьютере, где каждый кадр будет быстрее. Но за 1 секунду, несмотря на разное количество циклов, предмет пройдет одно расстояние.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

Ну смотри. Во многих играх понятия fps и tps принципиально разные. Потому как fps привязан к видяхе и всё что с ним связано решает только задачу рендеринга картинки. Как пример таких игр это minecraft, factorio, а есть серверный обсчёт того что происходит в игре, это tps. Например, когда у тебя в майнкрафте стоит чанк загруженных и работающих печек которые будут плавить 20 предметов в секунду, то рендер будет справляться без проблем, потому как всё что ему надо это рисовать горящие печки и состояние картинки изменится только когда печки перестанут плавить, если бы не анимация то можно было бы вообще картинку не перерисовывать всё это время. А вот tps может просесть, потому как ему надо просчитывать каждый плавящийся предмет 20 раз в секунду в каждой печке. Ну тут я утрирую, на самом деле из-за плохого рендера который прорисовывает невидимые блоки и потом сверху ещё блоки и ещё отрисовывая в конце только видимые тут именно он ляжет первым скорее всего. Но вод лагучий редстоун с какой-нибудь сложной схемой на пульсаре (конструкция которая заставляет переключаться схему как можно чаще, в идеале каждый тик) не fps сломает, а tps. Вот этот внутренний такт если в тех же кубах будет работать как-то не так, то весь ваш редстоун в тыкву превратится, но он работает нормально. Понятно что dota, poe, wow всё тоже самое имеют, просто сервера майна и факторки ты локально запускаешь ну и можешь их сам потыкать, а у тех игр они на стороне фирмы производителя игры крутятся. Потому я их и привёл в пример.

peregrine ★★★★★
()
Последнее исправление: peregrine (всего исправлений: 1)
Ответ на: комментарий от Norgat

Неткод это отдельная история, но там тоже самое можно применить, и применяют, плюс сверху различные интерполяции итд.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 1)
Ответ на: комментарий от peregrine

Во многих играх понятия fps и tps принципиально разные.

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

Если такт выполняется X сек. а предыдущий такт выполнился за 2X секунд, то есть «лаганул», то у тебя будет переменная 2.0, иногда ты можешь передать значение 2.0 в функцию обработки, и она сделает ускорение 2.0, иногда тебе нужно именно запускать одиночный такт, то можно завести счетчик тактов, к нему будет добавляться каждый раз переменная. Если предпредыдущий такт выполнился ровно, он у нас 0, теперь 0 + 2.0 = 2.0, выполняем за сейчас ровно два такта что бы «догнать».

Если предыдущий цикл выполнился слишком быстро, то будет 0 + 0.5 = 0.5, тактов не выполняем. А в следующий раз добавится 0.7, 0.5 + 0.7 = 0.12, выполнить нужно будет один такт, оставить 0.02.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 3)

Странный подход - делать паузы слипами.

Для этого же есть всякие технологии «запроса следующего кадра», когда твой код вызовут при перерисовке кадра. Даже в JS есть такие штуки, во всяких десктопных хернях точно должны же быть.

lesopilorama
()
Последнее исправление: lesopilorama (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

У тебя же получается есть доступ к циклу кадров, просто пропускай кадры

Фреймскип есть уже. Чтобы вы больше не спорили, логика с фпс не связана и работает со стабильной частотой. Профит от высокого фпс есть, потому что есть интерполяция движения. Чел уже проверил на 120 фпс монике.

Но это при vsync, а мне надо чтоб при любом установленном фпс стабильно было. Пока что я придумал только вычесть из задержки время тратящееся на слип в линуксе. Я при старте игры 100 раз гоняю слип и узнаю его погрешность

HPW-dev
() автор топика
Последнее исправление: HPW-dev (всего исправлений: 1)
Ответ на: комментарий от HPW-dev

Если ты уже умеешь пропускать кадры, что же мешает пропускать их для лимита фпс?

С vsync ты уже решил, как я понял. Допустим частота монитора 60, без vsync у тебя 500 фпс, тебе нужен лимит условно 250. Точно так же пропускай каждый второй?

Если время с предыдущего кадра < 1/250, то не рисовать.

uint64_t prev_frame_time = 0;
for (;;) {
  if (time_ns() - prev_frame_time > secf_to_ns(1.0f / 250.0f)) {
    prev_frame_time = time_ns();
    draw();
  }
}

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

А если задержка в несколько раз больше чем тебе надо?

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 9)
Ответ на: комментарий от lesopilorama

Странный подход - делать паузы слипами

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

vsync отлично справляется с синхронизацией, но нужно выводить указанное число кадров и стабильно

HPW-dev
() автор топика
Последнее исправление: HPW-dev (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA

С vsync ты уже решил, как я понял. Допустим частота монитора 60, без vsync у тебя 500 фпс, тебе нужен лимит условно 250. Точно так же пропускай каждый второй?

Если по времени потраченному на отрисовку кадра можно предположить сколько будет фпс, то наверное скип фреймов поможет сделать ограничение на нужное число кадров. Мне нужно это потестить.

А если задержка в несколько раз больше чем тебе надо?

Если на задержку не осталось времени, то она не вызовется. Лимит на задержку до секунды (мало ли, вдруг кому понадобится 1 фпс лимит)

HPW-dev
() автор топика
Ответ на: комментарий от HPW-dev

функция получения времени занимают много времени

Сколько именно?

#include <stdio.h>
#include <time.h>
#include <stdint.h>

#define N 1000

int main() {
  struct timespec ts;
  uint64_t prev, curr, diff[N];
  double avg;
  int i;

  prev = 0;
  avg = 0;
  for (i = -1; i < N; ++i) {
    clock_gettime(CLOCK_MONOTONIC, &ts);
    curr = ts.tv_sec * 1000000000 + ts.tv_nsec;
    if (i >= 0) {
      diff[i] = curr - prev;
    }
    prev = curr;
  }
  for (i = 0; i < N; ++i) {
    printf("diff[%d] = %.2fs : %zu\n", i, diff[i] / 1000000000.0, diff[i]);
    avg += diff[i];
  }
  avg /= N;
  printf("diff[avg] = %.2fs : %zu\n", avg  / 1000000000.0, (uint64_t)avg);
  return 0;
}

Мой результат

1000 diff[avg] = 0.00s : 87
1000000 diff[avg] = 0.00s : 10

Довольно быстрая функция.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)
Ответ на: комментарий от MOPKOBKA

Довольно быстрая функция

Я знаю, это у всех работает, но не у меня. Может мне просто надо поставить линукс заново. Я вытащил жёсткий диск с арчём из асуса с кор 2 дуо и подключил его в новый msi с райзеном и видимо арч не понял перемен.

Может мне надо удалить интел микрокод. Я уже попробовал выбрать другой таймер за место hpet. Обновлял граб конфиги, таймер acpi_pm такой же медленный. В винде 10 hpet работает, но тормозов с таймерами там нет. Мне надо что-то настроить в линуксе

HPW-dev
() автор топика
Последнее исправление: HPW-dev (всего исправлений: 2)
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.