Trying to Simulate Constant Byte Rate. Confusion with Time.Sleep Results

Trying to simulate constant byte rate. Confusion with time.sleep results

Conclusion

Thanks to @J.F.Sebastian and his code, I came to learned that:

  • Its better to use use a deadline as a time reference than create to a new reference each loop
  • Using a deadline "amortizes" the imprecision of time.sleep, oscilating a bit around the desired bitrate but resulting in an correct(and much more constant) average.
  • You only need to you use time.time() once, that means less calculations imprecisions.

As a result, I get a constant 32000 B/s some times oscilating to 31999 and very rarely to 31745

Now I can hear the music without any lag or jitter!

I tried using @J.F.Sebastian implentation using only the % operator to sleep the remainder, but the KB/s oscilate strangelly, so I decided to keep the deadline implementation, which suffers imprecision by keeping adding a float value. Nevertheless, the overall result is enough for my needs.

Thank you everyone.

Final Code

def read(self):
self.deadline += 0.020
delay = self.deadline - time.perf_counter()
if delay > 0:
time.sleep(delay)
return self._read()

Time.sleep inaccurate for Python counter?

Please note this quote from the Python documentation for time.sleep()

The actual suspension time may be less than that requested because any
caught signal will terminate the sleep() following execution of that
signal's catching routine. Also, the suspension time may be longer
than requested by an arbitrary amount because of the scheduling of
other activity in the system.

As a suggestion, if faced with this problem, I would use a variable to track the time that the interval starts. When sleep wakes up, check to see if the expected time has elapsed. If not, restart a sleep for the difference, etc.

Why do these two processes behave like this?

It is worth noting you would see the same behavior if you had somehow set p1.daemon = p2.daemon = True.

It is also possibly due to output buffering, rather than logic errors.
Two questions:

  • If you add a sys.stdout.flush() or flush=True to your print, do you see different behavior?
  • If you run this with time python foobar.py does it take .02s or 1s to run?

Obviously, continuing your tutorial and correctly adding .join() below will resolve the issue in a way that would be expected for normal usage.

Multiprocessing code not showing the desired results

I tried to execute your script with both command line and using the IDE (specifically Spyder) and I can observe the following differences in behavior.

When I execute the script using command line using python3 mp.py which has your code, I can see the output as expected:

Finished in  0.0  seconds
Sleeping 2 second(s)...

Finished in 0.0 seconds
Sleeping 2 second(s)...

Finished in 0.0 seconds
Sleeping 2 second(s)...

Finished in 0.0 seconds
Sleeping 2 second(s)...

Finished in 0.0 seconds
Sleeping 2 second(s)...
Done sleeping...
Done sleeping...
Done sleeping...
Done sleeping...
Done sleeping...

Finished in 3.7699832916259766 seconds

When I execute the same script using the Run button in Spyder IDE, I see only the following output:

Finished in  2.8714535236358643  seconds

I tried to look for the difference in behavior and found several threads that describe this behavior.

no-multiprocessing-print-outputs-spyder

There is a specific comments regarding Windows as well:

That printing with multiprocessing on Windows doesn't work as expected

Also, some IDEs require additional configuration as described for IDLE

Based on the above observations, I would suggest to try to run the script via command line to verify that the script itself work as expected and try to see if there are any additional configuration needed in the IDE to make it work.

Is using a loop to wait more CPU-intensive in python than a sleep command?

As @tobias_k mentioned in comment, try this:

import time
wakeuptime=time.time()+20 #start again exactly 20 seconds from now
UnpredictablySlowFunction()
time_left = wakeuptime -time.time()
time.sleep(time_left)

Make python process stop/wait/sleep for 0.2 seconds [Works]

Based on the time.sleep() documentation:

The actual suspension time may be less than that requested because any caught signal will terminate the sleep() following execution of that signal’s catching routine. Also, the suspension time may be longer than requested by an arbitrary amount because of the scheduling of other activity in the system.

So it works but there are no guarantees.

How do we use sleep() in Linux to keep our CPU usage reasonable while still having decent timing accuracy?

The only way to guarantee this is to use real-time OS scheduling. Otherwise, you are at the scheduler's mercy and could be preempted at any time (e.g. if there is some high-priority/low-nice process eating your CPU cycles). Indeed, sleep() is just a convenient way to ask for a preemption of a specific duration. It's always possible you will sleep for substantially longer than you ask. This is why Windows does not even try to sleep for <1ms; it isn't capable of that level of precision once you factor in scheduler nondeterminism. Out of the box, Linux isn't either, but it can be configured (via sched_setscheduler(2)) to be real-time, so it will make the attempt if you ask.



Related Topics



Leave a reply



Submit