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)
Ответ на: комментарий от MOPKOBKA

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

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

Возможно vdso тормозит по какой то причине? Попробуй чистый syscall, в Linux они не меняются, их безопасно использовать.

https://filippo.io/linux-syscall-table/

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

А, не увидел, ты выше уже писал про это. Тогда бесполезно скорее всего.

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

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

Я не очень понял проблему, подразумевающуюся после «но»…

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

Я тебе еще раз говорю - так не делают.

Если делать X, будет Y.

Да, но мне нужно без Y.

Ну так не делай X!

t184256 ★★★★★
()

chrt, к слову, совсем высокий приоритет для RR или FIFO не нужен, пока нет других процессов с такими твиками планировщика.

Но для обычной игры, это как избыточно.

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

Ээээ… я вот стараюсь и то и то делать. Но очевидно, что оптимально, далеко не всегда получается сделать красиво, кросс-платформенно и переносимо. Но когда нужно выжать все соки, тут уж или шашечки или ехать.

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

Ребята, вы про игрушку говорите. Какие нафиг игры с приоритетами? Если вы сложный реалтайм расчёт запустили со всякими OpenMP, то тогда уже да. И с аффинити играть нужно и RR/FIFO планировщики делать и приоритеты тюнить, и OpenMP по доменам раскидывать.

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

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

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

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

Если по времени потраченному на отрисовку кадра

И это время тебе не нужно знать.

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

Ээээ… я вот стараюсь и то и то делать. Но очевидно, что оптимально, далеко не всегда получается сделать красиво, кросс-платформенно и переносимо. Но когда нужно выжать все соки, тут уж или шашечки или ехать.

Ты тоже считаешь переназначение первоочерёдности исполнения процессов планировщиком умным решением для достижения точного по времени исполнения задачи, да?

Enthusiast ★★★
()

Попробуй просыпаться в указанное время через

clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &tp, NULL);

Я использовал на CLOCK_REALTIME, с постоянной синхронизацией часов системы, отсылая пакеты с частотой 2400 Гц. Всё очень ровно было (но вот только ещё был SCHED_FIFO, который явно не для тебя).

Из особенностей:

  • Нужно контролировать, что не вычисляешь дольше нужного интервала времени.
  • Просыпаться будешь всё равно чуть позже. Эту задержку можно учесть.
  • Можешь проспать дольше обычного, если тебя сместил другой процесс.
AlexVR ★★★★★
()
Ответ на: комментарий от HPW-dev

но нужно выводить указанное число кадров и стабильно

так считай кадры, в чём проблема? либо система вывозит либо нет…

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

Технически это опять сахарок над планировщиком как и слипы.

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

Я не очень понял проблему, подразумевающуюся после «но»

Я использую glfw, там есть функция glfwSwapInterval, она включает VSync. Ей можно указать только числовое значение, оно влияет на то, стоит ли включить vsync, выключить или пропускать каждый второй кадр и т.д., но нельзя выбрать свой лимит кадров, например 45 фпс.

Я написал свой лимит кадров, он работает через проверку таймера с учётом времени кадра и апдейта игры, задержка выставляется так, чтобы выходило столько фпс, сколько выставлено. Это работает на винде, но ломается на моём линуксе. Это что-то типа лимита кадров в майнкрафте или max_fps в cs1.6 или quake3

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

Но для обычной игры, это как избыточно

просто с nice эффекта не было, поэтому я писал это:

sudo nice -n 19 chrt --rr 99 ./game
HPW-dev
() автор топика
Последнее исправление: HPW-dev (всего исправлений: 1)
Ответ на: комментарий от HPW-dev

Четвёртый раз повторяю: напиши какие у тебя получаются тайминги кадров.

Нужные такие: 0 16.66 33.32 49.98 66.64

Какие у тебя (те, которые тебе не нравятся) и какие sleep-ы ты вызываешь между ними?

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

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

Я думаю тебе не надо «свой лимит кадров». Юзай что дают, так будет надёжнее. Пусть твой код вызывают на каждый кадр, ты просто будешь пропускать работу иногда, а иногда рисовать.

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

Нужные такие: 0 16.66 33.32 49.98 66.64

VSync

16,7257
33,4931
50,2164
67,1878
83,9207

Задержка винда

16,3243
33,4178
49,6852
66,6294
83,0257

Задержка линух без sleep с циклом с проверкой времени

21,4809
41,1134
63,066
83,1616
105,5841

Задержка линух со sleep и вычетом средней погрешности

16,8947
33,5661
50,2308
66,9003
83,5627
HPW-dev
() автор топика
Ответ на: комментарий от HPW-dev

Вот смотри, ты нарисовал первый кадр, сделал sleep(16.66) (это единственный раз когда слип должен быть именно таким), затем нарисовал второй и собираешься делать sleep опять. Смотришь текущее время - оно 21.4809. Знаешь, что третий кадр нужен на 33.32. Значит sleep надо ставить на 33.3200-21.4809 = 11.8391 мс. Нарисовал третий кадр, смотришь время - оно окажется какое-нить 37. Опять считаешь 49.98-37 = 12.98 - делаешь именно такой sleep. Никакие бенчмарки заранее проводить не нужно, просто на каждом кадре считай когда должен быть следующий описанным способом. В итоге разница между двумя соседними кадрами всегда будет именно такой как ты хочешь +- небольшие отклонения, которые усредняются в ноль.

То, что твой текущий способ (sleep без проверки времени) работает на винде - это считай везение: у тебя игра жрёт не сильно много проца, комп наверно мощный, и на компе ничего тяжёлого рядом не запущено. Как только нагрузка на проц вырастет, оно и на винде скорее всего поплывёт.

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

На видео можете заценить как сейчас выглядит лимит фпс (в видео есть таймкоды):

Видос: https://youtu.be/mlO7F02Ehuo

Короч на линухе слип плохо работает, было бы приятно с ним играть, так бы проц не грузился на 100%. На винде это работает. Использовал usleep с вычетом погрешности и без. Так же я попробовал нанослип монотонный и реалтайм, но там те же лаги

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

Я в Xash3D FWGS ещё надстройку над главным циклом сделал, чтобы сначала слипать небольшими интервалами, а по истечению времени переключаться на busy-wait.

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

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

Пон, попробую

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

То, что твой текущий способ (sleep без проверки времени) работает на винде - это считай везение: у тебя игра жрёт не сильно много проца, комп наверно мощный

Это работало и на core 2 duo e5400. Длительность задержки я вычислял по времени потраченному на апдейт и рендер в текущем фрейме, чтобы оставшееся время добить слипом

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

Я в Xash3D FWGS ещё надстройку над главным циклом сделал, чтобы сначала слипать небольшими интервалами, а по истечению времени переключаться на busy-wait.

А как определить сколько слипать?

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

Для этого у меня есть переменная которая хранит время чисто игрового кадра.

Если задача синхронизироваться по 60 тикам в секунду, а это 16.(6) миллисекунд, то из него вычитается чистое время кадра и небольшое окно для переключения в busy-wait. Окно можно двигать в зависимости от графика времени кадра, но у меня пока что проще.

Допустим, время кадра 1мс и окно например три таких кадра, то есть 3мс. Вычитая, получается 12.(6) мс. Далее, можно в этом интервале прогонять sleep, замеряя время самого sleep.

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

Кстати, забавно, но в моих экспериментах задержка в Windows была гораздо больше, чем в Linux.

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

Кстати, забавно, но в моих экспериментах задержка в Windows была гораздо больше, чем в Linux.

У меня такой код на винде медленный, но с лупом и проверкой времени задержка быстрая выходит

#include <cassert>
#include <algorithm>
#include <iostream>
#include <numeric>
#include <chrono>
#include <thread>
#include <vector>

using Time = double;
using Seconds = std::chrono::duration<double, std::ratio<1, 1>>;

void delay(const Time time) {
  std::this_thread::sleep_for( Seconds(time) );
}

int main() {
  auto tests = 20;
  std::vector<Time> statistic(tests);

  for (auto& it: statistic) {
    auto st = std::chrono::steady_clock::now();
    delay(1.0 / 60.0);
    auto ed = std::chrono::steady_clock::now();

    it = std::chrono::duration_cast<Seconds>(ed - st).count();
    assert(it > 0);
  }

  auto avg = std::accumulate(statistic.begin(), statistic.end(), 0.0l) / statistic.size();
  auto [min, max] = std::minmax_element(statistic.begin(), statistic.end());
  std::cout << "min: " << *min << std::endl;
  std::cout << "max: " << *max << std::endl;
  std::cout << "avg: " << avg << std::endl;
}

Один чел переписал это на расте и у него задержки меньше

use std::time::{Duration, Instant};
use std::thread::sleep;
use std::cmp::{min, max};

type Time = f64;

fn delay(time: Time) {
    let seconds = Duration::from_secs_f64(time);
    std::thread::sleep(seconds);
}

fn main() {
    let tests = 20;
    let mut statistic = vec![0.0; tests];

    for it in statistic.iter_mut() {
        let st = Instant::now();
        delay(1.0 / 60.0);
        let ed = Instant::now();

        *it = (ed - st).as_secs_f64();
        assert!(*it > 0.0);
    }

    let avg = statistic.iter().sum::<Time>() / tests as Time;
    let (min, max) = (statistic.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap(),
                      statistic.iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap());
    println!("min: {}", min);
    println!("max: {}", max);
    println!("avg: {}", avg);
}

Растовый таймер на винде юзает чё-то типа этого:

  ::LARGE_INTEGER ft;
  ft.QuadPart = -static_cast<std::int64_t>(time * 10'000'000);
  ::HANDLE timer = ::CreateWaitableTimerExW(NULL, NULL,
    CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
  ::SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
  ::WaitForSingleObject(timer, INFINITE);
  ::CloseHandle(timer);
HPW-dev
() автор топика
Ответ на: комментарий от HPW-dev

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

А, ну так ты почти всё правильно делал. Единственное, что надо изменить - это «потраченным» считать не только время рендера, но время опоздания предыдущего слипа от эталонного тайминга.

firkax ★★★★★
()

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

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

Это не тормоза и не костыли, а штатное поведение планировщика для не-realtime процессов и обычный способ поддержания фпс.

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

Так я согласен, что не делают, но конкретно задачу как политику планирования сменить для потока как решается показал вродь:)

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

Выкинь код который ты сюда запостил и сделай busy-wait.

Игрок не будет ориентироваться насколько у него «сломан» (на самом деле, нет) линукс. Так же и на винде это может выстрелить, опять же уже сказали это нормальное поведение планировщика.

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

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

Вот это мощный алгоритм. О_о

А зачем std::this_thread::yield()?

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

А зачем std::this_thread::yield()?

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

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

Мне интннесно, как сформироаался такой вывод?

Что лучше по-твоему: временная выдержка времени в своем процессе через чтение текущего времени и сравнения его с уставкой или межпроцессное таймерное прерывание?

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

А ничто не лучше. Всё зависит от поставленной цели и задачи.

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

auto last_process = std::chrono::steady_clock::now();
...
if (std::chrono::steady_clock::now() - last_process > 1s) {
  last_process = std::chrono::steady_clock::now();
  ... some work ...
}

будет достаточно.

Если нужен жёсткий реалтайм… То у меня первый вопрос: а точно вам нужна ОС общего назначения?

Если нужна, то дальше опять думать. И тут как бы man 2 ualarm / SIGALRM / SIGVTALRM неплохой выбор. Ещё и в select/epoll/io_uring можно добавить.

Сюда же и твой setitimer, а так же timer_settime, timer_create и иже с ними. Тоже порождает SIGALRM/SIGVTALRM (или пользователькие, см. timer_create).

Но твоё утверждение, что там всё чётко доставится некорректно. Очень хорошо об этом написано в man 3 timer_create с секции RATIONALE. Как минимум, приложение может прозевать сигнал, если пока работает обработчик оно опять пришло, или очередь для сигналов может быть переполнена (в случае RT сигналов), или используются RT сигналы, так они не пропускаются, и все друг за дружкой лягут, а как ресурсы освободятся, так и могут вылететь пачкой. Мрак.

Ну ок, с сигналами понятно: тут всё, как с прерываниями. Долго сидеть в обработчике нельзя, иначе будут проблемы. По классике делим на Top Half и Bottom Half. В обработчике сигнала только ставим флаг (и используем только функции и вызовы которые разрешены в обработчиках сигналов), остальную обработку делаем в основном потоке выполнения… А вот тут мы сталкиваемся с тем, что этот поток уже управляется основным планировщиком. И если кто-то «наляжет» на систему, то и обработка поплывёт и тайминги поедут.

Собственно, если это сильно специализированная машина, для которой важно выполнять твой код в режиме крайне приближенном к жёсткому RT, то тут и chrt с RR/FIFO правилами и приоритетами потребуется в дополнение ко всему выше перечисленному.

Другое дело, что для обычной игрушки это уже перебор. Да и банальный busy loop с проверкой последних изменений тут с большой долей вероятности будет достаточен. А если нужны таймеры, то можно воспользоваться Asio или чем-то похожим (только не из состава Boost). Ну или по low-level way, самому через сигналы.

А вот использовать sleep(), который, к слову, тоже может на SIGARLM сделан быть, для задержек в игровом цикле и надеяться на его временные оценки… Ну такое себе.

Я ответил на вопрос? :)

hatred ★★★
()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.