#Improve Precision of Fixed Frequency Loop

19 messages · Page 1 of 1 (latest)

rough arrow
#

Hi I have a working loop that tries to iterate at a fixed frequency. In reality it's more like the max frequency since there can be slow downs. but here it is:

void FixedFrequencyLoop::start(const std::function<void(double)> &rate_limited_func,
                               const std::function<bool()> &termination_condition_func,
                               std::function<void(IterationStats)> loop_stats_function) {

    auto period_ns = std::chrono::nanoseconds{static_cast<long long>(1'000'000'000.0 / max_update_rate_hz)};

    auto loop_start_time = std::chrono::steady_clock::now();
    auto time_at_start_of_last_iteration = loop_start_time;

    double total_time = 0.0;
    int count = 0;

    bool should_keep_running = true;

    while (should_keep_running) {
        GlobalLogSection _("ffl while loop", logging_enabled);

        auto current_time = std::chrono::steady_clock::now();
        auto elapsed = current_time - time_at_start_of_last_iteration;
        time_at_start_of_last_iteration = current_time;

        double measured_period = std::chrono::duration<double>(elapsed).count();

        total_time += measured_period;
        ++count;

        rate_limited_func(measured_period);

        if (rate_limiter_enabled) {
            GlobalLogSection _("sleep", logging_enabled);

            auto next_target_time = loop_start_time + period_ns * count;
            auto now = std::chrono::steady_clock::now();

            if (next_target_time > now) {
                std::this_thread::sleep_until(next_target_time);
            } else {
                // we're behind schedule, skip sleep to catch up
            }
        }

        should_keep_running = !termination_condition_func();
        iteration_count++;
    }
}
spring blazeBOT
#

When your question is answered use !solved or the button below to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.

rough arrow
#

Note that we're attempting to run at 1024hz, which gives a period of 1/1024 = 0.0009765625 seconds. In the diagram we have spikes up to 0.002 which is more than double that value.

I'm curious to know whether this kind of error is expected or if there is anything I can be doing better in the loop, thanks.

Also it appears as though the spikes are periodic if that helps.

spring blazeBOT
#

@rough arrow Has your question been resolved? If so, type !solved :)

wanton knot
#

You will always have noise, for many reasons. One of them is that other programs are also running on your computer, so the OS will sometimes schedule them to run on the core you're currently using and your program has to wait until it can run again

#

sleeping has the same effect, it asks the OS to schedule another program and you'll have to wait until your program can run again

#

there are a few things you can do to improve the sitation somewhat, like closing as many other programs as possible, letting the scheduler know that your program is important (on linux, you can use nice to give your program a higher priority) and pinning your process to a specific core and preventing other programs to use that core

#

instead of sleeping, you can also busy wait (basically while (not yet time) do nothing;), which means you're not relying on the OS scheduler to wake you up on time. But keep in mind that this will needlessly use resources, so you shouldn't do this for longer periods. A compromise would be to sleep until shortly before you want to resume, then busy wait until that time

rugged delta
# wanton knot instead of sleeping, you can also busy wait (basically `while (not yet time) do ...

A compromise would be to sleep until shortly before you want to resume, then busy wait until that time
A) It should be stated that this is not always possible, because sometimes you just cannot know when the next work can be done (e.g. if you wait for a network packet), so it's either busy-wait forever, or sleeping.
And
B) I believe some lock in the C++ library works by combining both to first busy-wait until it has busy-waited for a certain period of time, and if it hasn't been able to acquire the resource by then, then it sleeps until being awakened.

wanton knot
#

yup

rough arrow
#

I appreciate the feedback here.

  • Would it be fair to say that my code I currently have is probably as precise as I can get without busy waiting?
  • If I busy waited instead of sleeping it would probably be the most precise way to make a fixed frequency loop ?
rugged delta
#

Realistically speaking, why do you even sleep here?
This seems to be a short-running process that frequently needs to perform work. Sleeping is good for when you may not wake up for several seconds/minutes. If you just want to sleep for that short a time ([way] less than a millisecond), and it's not a super critical piece of software that is going to be repeatedly run, then I'd just go for the busy-wait solution. Has the additional benefit of keeping your code "hot".

runic topaz
#

also note that sleeping will not guarantee exact timing and only give you estimate sleeping duration and you just specify the minimum duration for sleeping

runic topaz
rugged delta
runic topaz
#

bussy wait

rough arrow
#

Thanks for the knowledge about sleep, i'll try to record the results of using busy wait and share the graph for comparison later on

spring blazeBOT
#

@rough arrow Has your question been resolved? If so, type !solved :)

ebon tendon
# rough arrow Hi I have a working loop that tries to iterate at a fixed frequency. In reality ...

i see three main things here that are gonna be a pain in prod. first is the "spiral of death", since you're calculating the target time based on the total count, if the computer sleeps or a frame hangs, the count falls way behind. when it wakes up, the loop is gonna spin at max speed with zero sleep trying to catch up on all those missed iterations. also, std::function is a bit heavy for a hot loop like this since it adds overhead and stops the compiler from inlining; templates are way faster there. lastly, stick to pure std::chrono for the internal math. mixing in doubles causes floating point drift over long uptimes