7

I'm looping 1000 times with time delay 1ms and calculating total time. It's very interesting how the total time is 15.6 seconds instead of 1. When I opened Google Chrome and surfed some websites, it ran correctly with 1 sec total. Also, it ran fine with Macbook too. I'm wondering what kind of solutions that I need to do to fix this problem? Please try to run it without Chrome opened an again with Chrome opened to see the difference. It ran normally when Quora or Reddit or Stackoverflow opened on my system.

from timeit import default_timer as timer
import time
start = timer()
for i in range(1000):
    time.sleep(0.001)

end = timer()
print ("Total time: ", end - start)

Edit: I didn't run it on Python. I just opened up Chrome and browsed some websites to speed up the time delay.

Updated: It's about the timer resolution from Windows. So basically, Chrome changed the timer resolution from 15.6ms to 1ms. This article explains very well: https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/

Iker Hua
  • 403
  • 4
  • 14
  • How are you running python in chrome? This is very confusing. – Eli Sadoff Nov 14 '16 at 17:34
  • 2
    Your mistake is assuming that `sleep(0.001)` will sleep for exactly a millisecond, rather than just *at least* 1ms. You might want to read http://stackoverflow.com/questions/9518106 – Jon Skeet Nov 14 '16 at 17:36
  • To Eli: I didn't run it on Python. I just opened up Chrome and browsed some websites to speed up the time delay. Sorry for the confusion. – Iker Hua Nov 14 '16 at 17:42
  • @IkerHua: why would that increase the time delay? – Martijn Pieters Nov 14 '16 at 17:44
  • 1
    -Jon: I understand it won't be exactly a millisecond. But the uncertainty is huge. When I ran it when Chrome is opened, it took 1.003 second to loop 1000 times. When Chrome is not opened, it took 15.6 seconds to loop 1000 times. And there's no modification in code between those two trials. That's what confused me the most. – Iker Hua Nov 14 '16 at 17:44
  • @MartijnPieters: Honestly, I have no clue. It's just very weird. Basically speaking, the program runs as expected when Chrome is opened. Otherwise, it would run extremely slow. And it only happens on Windows systems, both laptop (win 7) and desktop (win 10). – Iker Hua Nov 14 '16 at 17:46
  • @IkerHua: sounds like you have an interrupt issue; Chrome does a lot of I/O. – Martijn Pieters Nov 14 '16 at 17:55
  • Looks like the Windows `sleep` implementation is indeed [interrupt based](https://randomascii.wordpress.com/2013/04/02/sleep-variation-investigated/). – Martijn Pieters Nov 14 '16 at 17:57
  • @MartijnPieterst: I read the link provided by Jon above, and it talked about the timer resolution. And it seems like Chrome did activate the 1ms resolution. Otherwise, its default value is ~15 or 16ms. That's why I got 1 when Chrome is on and 15.6 when Chrome is off. – Iker Hua Nov 14 '16 at 18:00
  • @MartijnPieters: That's a very good article. Thanks man!!! Now I need to figure out how to increase the timing resolution from the OS. – Iker Hua Nov 14 '16 at 18:01
  • @IkerHua Instead, just check the time before you call `sleep`. – David Schwartz Nov 14 '16 at 18:09

1 Answers1

9

I finally figured it out. Thanks a lot for the comments. Those gave me hints to solve it. To explain why this happened, Windows OS has its default timer resolution set to 15.625 ms or 64 Hz, which is decently enough for most of the applications. However, for applications that need very short sampling rate or time delay, then 15.625 ms is not sufficient. Therefore, when I ran my program itself, it's stuck at 15.6 s for 1000 points. However, when Chrome is opened, the higher resolution timer is triggered and changed to 1 ms instead of 15.6, which caused my program to run as expected.

Therefore, in order to solve it, I needed to call a Windows function named timeBeginPeriod(period) to change the resolution timer. Fortunately, python made it easy for me to fix it by providing ctypes library. The final code is provided below:

from time import perf_counter as timer
import time
from ctypes import windll #new

timeBeginPeriod = windll.winmm.timeBeginPeriod #new
timeBeginPeriod(1) #new

start = timer()
for i in range(1000):
    print (i)
    time.sleep(0.001)

end = timer()
print ("Total time: ", end - start)

Warning: I read about how this high timer resolution will affect the overall performance and also the battery. I have not seen anything happening yet, and CPU Usage on Activity on Windows Task Manage doesn't seem to overwhelming either. But keep that in mind if your applications happen to cause some strange behaviors.

Iker Hua
  • 403
  • 4
  • 14
  • 2
    A [cleaner implementation](http://stackoverflow.com/a/38488544/205580) with error checking and context management. Of course, this is really unreliable for anything critical because it's at the mercy of other software running on the system. If you need critical timing you're better of busy looping with `time.perf_counter()` rather than relying on the system scheduler. – Eryk Sun Nov 15 '16 at 10:06
  • @eryksun: Hi eryksun, thank you for your suggestion. Would your method guarantee the exact time delay per cycle? Mine is still off by 0.1 or 0.2 milliseconds, and it's kind of bad because my application is to plot points at constant rate. Just a bit off from the rate would make the plot look really bad. – Iker Hua Nov 15 '16 at 14:31
  • On Windows, `timer.perf_counter` calls [`QueryPerformanceCounter`](https://msdn.microsoft.com/en-us/library/ms644904), which is a monotonic clock with at least 1 microsecond resolution. Check `time.get_clock_info('perf_counter')`. – Eryk Sun Nov 15 '16 at 14:53
  • I read and figured that `timer.perf_counter` is very powerful and accurate. But how do you use `time.perf_counter()` to delay or sleep in python? My understanding is it can be used to measure the time difference only. – Iker Hua Nov 15 '16 at 16:46
  • A "sleep" necessarily involves the kernel scheduler, which puts you at the mercy of the system timer resolution. If you need precision, then simply loop for the period of time, e.g. `deadline = time.perf_counter() + period;` `while time.perf_counter() < deadline: pass`. – Eryk Sun Nov 15 '16 at 18:31