I'm coding a simple piano roll app that uses the SDL2 bindings for Rust to do its event handling and rendering. I have something very similar to the following code:
let fps = 60;
let accumulator = 0; // Time in seconds
'running: loop {
let t0 = std::time::Instant::now();
poll_events();
if accumulator > 1.0 / fps {
update();
render();
counter -= 1.0 / fps;
}
let t1 = std::time::Instant::now();
let delta = (t1 - t0).as_secs_f64();
accumulator += delta;
// Busy wait?
}
Generally, the application is running fine and it doesn't have any noticeable artifacts, at least to my untrained eye. However, the CPU usage is through the roof, using nearly 25% in average (plus some GPU usage for the rendering).
I've checked the CPU usage of a very similar program which has many more features and also has better graphics, and when given the same MIDI notes to display, it averages at 2% CPU usage, plus 10% on the GPU side.
I also benchmarked my code, and found out the following approximated timings:
Given that I'm aiming for 60 fps at both the logic level and the rendering level, I have roughly 16 milliseconds to do a full poll/update/render cycle. At the moment I'm using about 2 milliseconds (being generous) of the full range, so my conclusion is that the main loop has some busy wait going on, which I want to get rid of.
I've tried mainly sleep-based solutions which are quite unreliable, since the sleeping time depends on the operating system and at least in my machine (Windows 11) it's around 10-20 milliseconds, causing noticeable delays in the animation.
From what I read there are some thread-related solutions to avoid this kind of situation, but I feel like it's a totally unnecessary area to get into since I'm way below the point of needing any concurrency to squeeze more performance out of the machine.
I've been learning Rust for a couple of weeks, and although I've used SDL2 before in a smaller project using C++, I had the same problem and I couldn't find a suitable solution.
I'm not sure if this is a problem specifically related to SDL2 or if it also happens using other libraries, but any help would be very appreciated.
As another post had already discussed some versions of Windows use a 15ms sleep time by default, but the OS does have a more precise sleep time which can be configured down to about 0.5ms.
There is a Rust crate that allows you to access a more precise timer by using the OS timer plus a little bit of busy wait for the remainders. That said, I didn't use this feature, as the native_sleep()
function already gives me the resolution I needed.
The updated code looks something like this:
let fps = 60.;
let accumulator = 0.; // Time in seconds
'running: loop {
let t0 = std::time::Instant::now();
poll_events();
if accumulator > 1.0 / fps {
update();
render();
counter -= 1.0 / fps;
}
// Fix
let sleep_time = std::time::Duration::from_millis(1);
spin_sleep::native_sleep(sleep_time);
let t1 = std::time::Instant::now();
let delta = (t1 - t0).as_secs_f64();
accumulator += delta;
}