Skip to content
Snippets Groups Projects

Use nanosleep for I_SleepDuration on *nix

Merged Hanicef requested to merge Hanicef/SRB2Classic:use-unix-nanosleep into next
2 unresolved threads

most unix systems use something called a "high-resolution timer" which is a timer that ticks at high frequency (as the name implies). this timer is the same timer that is used by the wall clock, and as such, is able to achieve the same accuracy as the wall time, which is often on microsecond accuracy. this makes it an ideal solution for frame capping, since the CPU is actually sleeping while we're capping. on my system, when capping the game to 35 fps, SRB2's CPU usage goes down to around 20% during gameplay, in contrast to the previous solution which normally used more than 85% CPU.

the only catch is that it's not supported on Windows, and i also haven't been able to test that Mac OS is using the same technique for nanosleep (if someone can verify, please do!). because of that, Mac OS (for now, at least) and Windows will remain using the old frame capping logic.

as for the accuracy, well... img

...and for reference, the old solution... img2

...it's pretty damn accurate. not perfect, but you aren't going to notice such a small difference at 300 fps anyway.

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
  • Hanicef added 1 commit

    added 1 commit

    Compare with previous version

    • Let look at Debian man page about clock_nanosleep(2)

      Standard C library (_libc__-lc_), since glibc 2.17

      Before glibc 2.17, Real-time library (_librt__-lrt_)

      So, we need GLIBC library for this function.

      We do not use GLIBC on all Linux/GNU, so you may need add a check for this function.

      So, we may run into a issue with alpine, which use musl as their C library

      So, I do not think MacOS will have this, they do not use GLIBC

      Now for FreeBSD, it look like it been there since 11.1

      https://man.freebsd.org/cgi/man.cgi?query=clock_nanosleep&apropos=0&sektion=0&manpath=FreeBSD+11.1-RELEASE+and+Ports&arch=default&format=html

    • Author Contributor

      So, we need GLIBC library for this function.

      no, we don't. clock_nanosleep is part of the POSIX standard: https://pubs.opengroup.org/onlinepubs/9699919799/functions/clock_nanosleep.html

      also, musl has clock_nanosleep too: https://git.musl-libc.org/cgit/musl/tree/src/time/clock_nanosleep.c

      i wouldn't use a function that's non-standard, and considering that it was standardized in 2000, i highly doubt that Mac OS wouldn't it, either. the only concern i have is the accuracy on Mac OS, which remains to be tested.

    • Ahh, I see.

      I have questions on how to handle errors from clock_nanosleep but looping on EINTR will do.

      But for MacOS, It looks like the function exists in 10.12 and higher, (Released on September 20, 2016)

      You will get this funny runtime error when it is ran on 10.11:

      dyld: lazy symbol binding failed: Symbol not found: _clock_nanosleep

      and the bad news, libSDL2's XCode project had set MACOSX_DEPLOYMENT_TARGET to 10.11

      We will break support for OS X El Capitan, but that OS is old, right?

    • Author Contributor

      I have questions on how to handle errors from clock_nanosleep but looping on EINTR will do.

      no need to handle any other errors. this is what can be returned from clock_nanosleep (taken directly from manpages):

             EFAULT request or remain specified an invalid address.
      
             EINTR  The sleep was interrupted by a signal handler; see signal(7).
      
             EINVAL The value in the tv_nsec field was not in the range [0, 999999999] or tv_sec was negative.
      
             EINVAL clockid was invalid.  (CLOCK_THREAD_CPUTIME_ID is not a permitted value for clockid.)
      
             ENOTSUP
                    The kernel does not support sleeping against this clockid.

      EFAULT can't happen since we're using a valid address, EINTR is already handled, EINVAL can't happen either since we're clamping to 999999999 using modulo and passing a constant to clockid, and ENOTSUP is highly unlikely since both Linux and FreeBSD fully support CLOCK_MONOTONIC, which is our target OSes for this PR at the moment.

      We will break support for OS X El Capitan, but that OS is old, right?

      7 years was a long time ago, yes, but the question is just how many this is going to affect. i don't know how many Mac OS players SRB2 has, let alone how many that runs 10.11 or earlier. however, this is all under the assumption that clock_nanosleep will even work as intended on Mac OS, since if it doesn't, there's no point in discussing it further. so, let's just check if it even works before we make the choice on dropping Mac OS 10.11 or not.

    • Contributor

      IIRC the modern CMake implementation needs to target macOS 10.13 or greater to build dependencies without errors, so I think standardizing that across the codebase would be fine.

    • Please register or sign in to reply
  • Logan Aerl Arias mentioned in commit 3ba48ba4

    mentioned in commit 3ba48ba4

  • Hanicef mentioned in merge request !2242 (merged)

    mentioned in merge request !2242 (merged)

2278 struct timespec ts = {
2279 .tv_sec = duration / precision,
2280 .tv_nsec = duration * 1000000000 / precision % 1000000000,
2281 };
2282 int status;
2283 do status = clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, &ts);
2284 while (status == EINTR);
2285 #else
2286 UINT64 precision = I_GetPrecisePrecision();
2287 INT32 sleepvalue = cv_sleep.value;
2288 UINT64 delaygranularity;
2289 precise_t cur;
2290 precise_t dest;
2291
2292 {
2293 double gran = round(((double)(precision / 1000) * sleepvalue * MIN_SLEEP_DURATION_MS));
  • Hanicef mentioned in merge request !2583 (merged)

    mentioned in merge request !2583 (merged)

  • Please register or sign in to reply
    Loading