4

I would like to try some hardware testing with python. I have to send commands to the hardware where there is a specification for consecutive bytes transmission time interval (~100-200us). However, I found the sleep() method in time module unstable when the delay time is too small. Is there any way/operations that takes ~100-200us?

I also wonder what is the accuracy of the function time(), can it really time a 100us interval?

from time import time, sleep
a = []
start = time()
for i in range(10):
    sleep(200e-6)
    a.append(time()-start)

this is what 'a' looks like:

[0.0010325908660888672,
 0.004021644592285156,
 0.006222248077392578,
 0.008239507675170898,
 0.009252071380615234,
 0.01157999038696289,
 0.013728857040405273,
 0.014998674392700195,
 0.016725540161132812,
 0.0187227725982666]
CypherX
  • 7,019
  • 3
  • 25
  • 37
Mayan
  • 492
  • 4
  • 11
  • 2
    I guess your measurement has a large impact on the result already. Use the `timeit` module to get a more acurate measurement. – Klaus D. Oct 25 '19 at 06:19
  • 1
    A desktop operating system has lots of things going on as well as Python executing your code - the mouse moves, The clock ticks over a second/minute so the displayed clock is automatically updated,other programs do stuff, there is i/o on the network, etc. so Yu will always is different from a realtime operating system so while you can specify the value to `time.sleep()` as precisely as you want, that is the _minimum_ delay you will get, not the maximum. – DisappointedByUnaccountableMod Oct 25 '19 at 06:24
  • Have you read the documentation for `time.sleep()`? https://docs.python.org/3/library/time.html – DisappointedByUnaccountableMod Oct 25 '19 at 06:27
  • @KlausD. I tried your suggestion. Seems that the lower limit is 1.7ms for the sleep() function.@barny I tried to mannually set the priority of the python programme to high in the task manager. The time between consecutive sleep is more stable, though the lower limit is ~1.7ms – Mayan Oct 25 '19 at 06:33
  • Linux or Windows is not a real-time or predictable environment timing-wise when you get to sub-10ms-ish intervals. Try doing some heavy downloading see how your timing stability varies? – DisappointedByUnaccountableMod Oct 25 '19 at 15:21

3 Answers3

2

About accuracy of time.sleep

It looks like time.sleep() only has millisecond resolution. Though it could range between a few milliseconds to over 20 milliseconds (best case scenarios), based on what OS you are using, it does not look like you will get accurate microsecond sleep execution from it.

References

  1. Must See This: How accurate is python's time.sleep()?
  2. usleep in Python
  3. https://github.com/JuliaLang/julia/issues/12770
  4. https://gist.github.com/ufechner7/1aa3e96a8a5972864cec

Another Option:

You could potentially then look for creating a C/C++ wrapper around the timer-code to use it from Python.

CypherX
  • 7,019
  • 3
  • 25
  • 37
1

The time module is not unstable, it is the underlying OS that you are running on. The request of sleeping is just passed on to the OS. In the documentation, one can read that the actual sleep time may be less then requested as any caught signal suspends the sleep, or it can be longer than requested as of scheduling reasons.If you need very high accuracy, you can use an Real-Time OS.

Daniel
  • 81
  • 3
-1

The minimum and accuracy of time.sleep is depending on the OS and python build package. But, normally it's limited in millisecond level.

There is usleep function for microsecond sleep which is from foreign function library. But, the function is blocked and changed both behavior and accuracy across other OS, and also conflict to some python versions. There are also several way to call short sleep and delay function on other C C++ libraries, but it'll crash when you put a little load which is unusable for deploying application.

However, there are still tricks to reach that short of delay. There are 2 kinds using delay: delay one time and delay repeat frequently.

  • If the application purpose is for testing or it only need short delay sporadically, then we can use a simple loop to sleep and performance counter to check the time exit sleep. (Performance counter is clock with the highest available resolution to measure a short duration. It does include time elapsed during sleep and is system-wide.)
  • In another case, if the application need to call it very frequently and the goal is to distribute final app to client with high performance, CPU energy saving. We will need multiple thread and even multiple process to apply sleep millisecond, then syncronize on starting point with different micro...nano second and compare via performance counter to produce stable fast clock program. This will be complex and depend on the application to design structure.

The function for simple case is simple:

import time;

def delayNS(ns, t=time.perf_counter_ns): ## Delay at nano second
    n=t(); e=n+ns;
    while n<e : n=t(); ## Dream Work takes so much energy :)

CAUTION that when you break down the stable minimum sleep limit, the delay effect cause by computation and OS background app and hardware communicate will rise up. You will need to short the name of class and variable and function and use simple syntax in script to reach highest speed as posible.

Below is the delay function that easier to read, but slower:

def sleepReal(dSec, upTime=time.perf_counter):## Can be short as 0.0000001 second or 100ns. The shorter, the less accurate
    now=upTime();  end=now+dSec;
    while now<end : now=upTime(); #Dream Work
  • function time.perf_counter() will return value (in fractional seconds) of a performance counter.
  • function time.perf_counter_ns() will return value (in nanoseconds) of a performance counter. This function is better because it avoid the precision loss caused by the float type. And also faster in real test.

Before apply function, you should check the delay effect when break the limit:

##### CalSpeed.py #####
import time, random;
ran=random.random;
upTIME=time.perf_counter_ns; ## More accuracy and faster
##upTIME=time.perf_counter;
def Max(x,y): return (x if x>y else y);

#####################
i=0;
nCycleUpdate=100000;   ### <<< EDIT CYCLE PRINT OUT, LET'S CHANGE IT TO  1, 10, 100, 1000, 10000, 100000, 1000000
def DreamWorks(): ### <<< YOUR DREAM WORK TO CHECK PERFORMANCE
    return True;
    #Yawwwww=ran()*ran(); ## < ~77% speed of return True
    #pass; ## < ~92% speed of return True
#####################

crT=upTIME();
limLog=nCycleUpdate-1;
iCycle=0;
def logSpeed():
    global crT, limLog, iCycle, nCycleUpdate,  i;
    iCycle+=1;
    i+=1;
    ###DreamWorks(); ### <<< YOUR WORK < ~72% speed of not dreaming
    if iCycle>limLog:
        iCycle=0;
        icrT=upTIME();
        dT=(icrT-crT)/nCycleUpdate;
        print("Count: "+str(i)+"   dT= "+str(dT)+" ns   T= "+str(icrT/1000000000)+" s   fdT= "+str(round(1000000000/Max(0.001,dT)))+" Hz   fR= "+str(i*1000000000/icrT)+" Hz" );
        ##print("Count: "+str(i)+"   dT= "+str(dT*1000000000)+" ns   T= "+str(icrT)+" s   fdT= "+str(round(1/Max(0.000000000001,dT)))+" Hz   fR= "+str(i/icrT)+" Hz" );
        #print("f = "+str(i*1000000000/icrT)+" Hz"); # < not much difference due to the costly divide calculate
        crT=icrT;
        

while True : logSpeed();

Result of check calculate speed: enter image description here

When you change the nCycleUpdate larger, the display work will less and it complete more compute task. You can try resize/maximize the terminal window and add print task to see the change speed effect. If you try to paint or write file, the task complete will decrease more. So, the delay task for simple case is a check time task that has less computation step as possible.

enter image description here


Bellow is application of simple delayNS function. I created setIntervalNS(f,ns) for easy create thread in python with custom loop cycle down to microseconds and nanoseconds:

##### DIYDelay.py #####

import time, random;
ran=random.random;
upTIME=time.perf_counter;
upTimeNS=time.perf_counter_ns;
def Max(x,y): return (x if x>y else y);

def sleepReal(dSec, upTime=time.perf_counter):## Can be short as 0.0000001 second or 100ns. The shorter, the less accurate
    now=upTime();  end=now+dSec;
    while now<end : now=upTime();#Dream Work
    #while upTime()<end : pass;#Dream Work
    #while upTime()<end : continue;#Dream Work

def delayNS(ns, t=time.perf_counter_ns): ## Delay at nano second
    n=t(); e=n+ns;
    while n<e : n=t(); ## Dream Work take so much energy :)
    #while t()<end : pass;#Dream Work
    #while t()<end : continue;#Dream Work

##///////////////////////////////////////
##////////// RUNTIME PACK LITE //////////  FOR PYTHON
##
import threading;
class THREAD:
    def __init__(this):
        this.R_onRun=None;
        this.thread=None;
    def run(this):
        this.thread=threading.Thread(target=this.R_onRun);
        this.thread.start();
    def isRun(this): return this.thread.isAlive();

AIntervalNS=[];
class setIntervalNS :
    def __init__(this,R_onRun,nsInterval) :
        this.ns=nsInterval;
        this.R_onRun=R_onRun;
        this.kStop=False;
        this.kPause=False;
        this.thread=THREAD();
        this.thread.R_onRun=this.Clock;
        this.thread.run();
        this.id=len(AIntervalNS); AIntervalNS.append(this);
    def Clock(this) :
        while not this.kPause :
            this.R_onRun();
            delayNS(this.ns);
    def pause(this) :
        this.kPause=True;
    def stop(this) :
        this.kPause=True;
        this.kStop=True;
        AIntervalNS[this.id]=None;
    def resume(this) :
        if (this.kPause and not this.kStop) :
            this.kPause=False;
            this.thread.run();
    
def clearAllIntervalNS():
    for i in AIntervalNS:
        if i!=null: i.stop();
        
###########
### END ### RUNTIME PACK LITE ###########
###########


from datetime import datetime;
timeNOW=datetime.now;

#####################
i=0;
nCycleUpdate=1;   ### <<< EDIT CYCLE PRINT OUT, LET'S CHANGE IT TO  1, 10, 100, 1000, 10000, 100000, 1000000
def DreamWorks(): ### <<< YOUR DREAM WORK TO CHECK PERFORMANCE
    return True;
    #Yawwwww=ran()*ran(); ## < ~77% speed of return True
    #print(str(Yawwwww));
    #pass; ## < ~92% speed of return True
#####################


crT=upTimeNS(); crTS=timeNOW(); crTS0=crTS;
limLog=nCycleUpdate-1;
iCycle=0;
def setCycleCheck(n):
    global nCycleUpdate, limLog;
    nCycleUpdate=n; limLog=nCycleUpdate-1;
def logSpeed():
    global crT, crTS, limLog, iCycle, nCycleUpdate,  i;
    iCycle+=1;
    i+=1;
    ##DreamWorks(); ### <<< YOUR WORK < ~72% speed of not dreaming
    if iCycle>limLog:
        iCycle=0;
        icrT=upTimeNS();
        icrTS=timeNOW();
        dT=(icrT-crT)/nCycleUpdate;#cycle ns per batch task completed
        dTS=(icrTS-crTS).total_seconds()/nCycleUpdate;
        #print("Checked: "+str(i)+"   dT= "+str(dT)+" ns   T= "+str(icrT/1000000000)+" s   fdT= "+str(round(1000000000/Max(0.000001,dT)))+" Hz   fR= "+str(1000000000*i/icrT)+" Hz" );
        #print("Task: "+str(i)+"   dT= "+str(dT)+" ns   T= "+str(icrT/1000000000)+" s   fdT= "+str(round(1000000000/Max(0.000001,dT)))+" Hz   fR= "+str(1000000000*i/icrT)+" Hz   dTS= "+str(dTS*1000000000)+" ns   fdTS= "+str(round(1/Max(0.000000000001,dTS)))+" Hz   fRS= "+str(i/((icrTS-crTS0).total_seconds()))+" Hz" );
        #print("Task: "+str(i)+"   dT= "+str(round(dT))+" ns   T= "+str(round(icrT/1000000000,6))+" s   fdT= "+str(round(1000000000/Max(0.000001,dT)))+" Hz   fR= "+str(round(1000000000*i/icrT))+" Hz   dTS= "+str(round(dTS*1000000000))+" ns   fdTS= "+str(round(1/Max(0.000000000001,dTS)))+" Hz   fRS= "+str(round(i/((icrTS-crTS0).total_seconds())))+" Hz   dR= "+str(round(icrT/i))+" ns" );
        #print("Task: "+str(i)+"   dT= "+str(round(dT/1000,3))+" µs   T= "+str(round(icrT/1000000000,6))+" s   fdT= "+str(round(1000000000/Max(0.000001,dT)))+" Hz   fR= "+str(round(1000000000*i/icrT))+" Hz   dTS= "+str(round(dTS*1000000,3))+" µs   fdTS= "+str(round(1/Max(0.000000000001,dTS)))+" Hz   fRS= "+str(round(i/((icrTS-crTS0).total_seconds())))+" Hz   dR= "+str(round((icrT/i)/1000,3))+" µs" );
        print("Task: "+str(i)+"   dT= "+str(round(dT/1000,3))+" µs   T= "+str(round(icrT/1000000000,3))+" s   dRealAverage= "+str(round((icrT/i)/1000,3))+" µs" );
        crT=icrT;
        crTS=icrTS;
        
        
        
#####################
    
#setCycleCheck(1); setIntervalNS(logSpeed,1000000000); ## 1 second
#setCycleCheck(1); setIntervalNS(logSpeed,100000000); ## 100 ms  ## 10Hz
#setCycleCheck(1); setIntervalNS(logSpeed,20000000); ## 20 ms  ## 50Hz
#setCycleCheck(1); setIntervalNS(logSpeed,10000000); ## 10 ms  ## 100Hz ## slowdown 10.6 ms
#setCycleCheck(1); setIntervalNS(logSpeed,1000000); ## 1 ms  ## measure calculate and print slowdown to 1.56 ms
#setCycleCheck(1); setIntervalNS(logSpeed,500000); ## 500 µs  ## slowdown to 850 µs
#setCycleCheck(1); setIntervalNS(logSpeed,200000); ## 200 µs  ## slowdown to 500 µs

setCycleCheck(1); setIntervalNS(logSpeed,100000); ## 100 µs  ## slowdown to 200 µs   ## Make background app and you will reach nearly pure 100µs delay
##setCycleCheck(1); setIntervalNS(logSpeed,10000); ## 10 µs  ## slowdown to 100 µs

#setCycleCheck(1); setIntervalNS(logSpeed,1000); ## 1 µs  ## reach 160 µs  ## Limit calculate log and display task
#setCycleCheck(10); setIntervalNS(logSpeed,100000); ## 100 µs  ## reach 176 µs delay silence task

##setCycleCheck(100); setIntervalNS(logSpeed,100000); ## 100 µs  ## reach 106 µs delay silence task

#setCycleCheck(100); setIntervalNS(logSpeed,1000); ## 1 µs  ## reach 8 µs ## Slowdown by calculate and print
#setCycleCheck(100000); setIntervalNS(logSpeed,1000); ## 1 µs  ## reach 2.8 µs
#setCycleCheck(10000000); setIntervalNS(logSpeed,1000); ## 1 µs  ## reach 2.75 µs
#setCycleCheck(10000000); setIntervalNS(logSpeed,100); ## 100 ns  ## reach 1950 ns
#setCycleCheck(10000000); setIntervalNS(logSpeed,1); ## 1 ns  ## reach 1880 ns

You can uncomment and comment code to change the print and task configuration to see accuracy efficiency. This is the result when try delay 100µs while maximize console and display task by task with most detail: enter image description here

Task: 80673   dT= 255.1 µs   T= 34.029082 s   fdT= 3920 Hz   fR= 2371 Hz   dTS= 255.0 µs   fdTS= 3922 Hz   fRS= 2376 Hz   dR= 421.815 µs
Task: 80674   dT= 239.3 µs   T= 34.029321 s   fdT= 4179 Hz   fR= 2371 Hz   dTS= 0.0 µs   fdTS= 1000000000000 Hz   fRS= 2376 Hz   dR= 421.813 µs
Task: 80675   dT= 251.8 µs   T= 34.029573 s   fdT= 3971 Hz   fR= 2371 Hz   dTS= 492.0 µs   fdTS= 2033 Hz   fRS= 2376 Hz   dR= 421.811 µs
  • dT : delta Time calculate by the different performance counter each cycle
  • T : total Time running count by performance counter
  • fdT : frequency from dT
  • fR : real frequency complete task = total task complete / total time running measured by performance counter
  • dTS : delta Time System calculate by get different datetime.now method
  • fdTS : frequency from dTS
  • fRS : real frequency calculate System = total task complete / total time running measured by datetime.now method
  • dR : average delta Real delay time = total time running measured by performance counter / total task complete

So, dT is close to correct instance delay time. And using datetime or get timestamp will be slower to measure the delay.

Test delay 100µs with smaller console and simplier calculation: enter image description here

Here is a test of delay 100µs which is only print out status each 100 time finish the task:

setCycleCheck(100); setIntervalNS(logSpeed,100000); ## 100 µs  ## reach 104 µs delay silence task

enter image description here

delay 1µs:

setCycleCheck(100000); setIntervalNS(logSpeed,1000); ## 1 µs  ## reach 2.3 µs

enter image description here

delay 100ns, it can't delay faster on my device (Intel Core i3-2370M CPU @ 2.4GHz 2.4 GHz):

setCycleCheck(10000000); setIntervalNS(logSpeed,100); ## 100 ns  ## reach 1950 ns

enter image description here


Single delay test:

enter image description here

Test delay 100us with simple print task : enter image description here

Finally, you can reach higher accuracy by run as background process.

phnghue
  • 1,578
  • 2
  • 10
  • 9