Reputation: 31
I made some programs in C using Dev C++ that use the usleep
function, which prints characters individually slower or faster, set by a number I want:
void lyrics(char *s, unsigned ms_delay){
unsigned usecs = ms_delay * 1000; /* 1000 microseconds per ms */
for (; *s; s++) {
putchar(*s);
fflush(stdout);
usleep(usecs);
}
}
When I ran them on Windows 7 they were perfect and the times were respected.
On Windows 10, it just prints the characters very slowly, 1 being the fastest, but still way slower than it should.
I've tried running as an admin, compatibility mode, affinity to 1 CPU core but those don't seem to work, and I don't have much knowledge of C to try other methods.
And it's used here to print each character at a certain speed, 200ms each being the slowest:
lyrics("We are ", 150);
lyrics("flying ", 190);
lyrics("high\n\n", 200);
Am I missing something or is it a Windows 10 thing?
I also am using C and not C++. A lot of the functions recommended and linked to my previous post are for C++ and will not run under C and vice-versa.
Edit: Minimal example code
#include <stdio.h>
void lyrics(char *s, unsigned ms_delay){
unsigned usecs = ms_delay * 1000;
for (; *s; s++) {
putchar(*s);
fflush(stdout);
usleep(usecs);
}
}
main(){
lyrics("\
DDDDDDDDD CCCCCCCCC XXX XXX\n\
DDDDDDDDDD CCCCCCCCC XXX XXX\n\
DDD DDDD CCCCC XXX XXX\n\", 1);
}
Edit 2: I found something peculiar. My program is quite extensive because it prints lyrics at different speeds and prints graphics made with ASCII.
When printing at 1, supposedly 1ms per character, it prints very slowly on 10, but when it plays the lyrics, it's the same speed as in 7. I changed the minimal code to reflect what I found and the videos below.
I also made a YouTube video showcasing my program before, which ran under Win7, and made a new one under 10 to show the difference and hopefully illustrate the situation better:
Under Win7 (normal)
I've tried to individually print each line with a 1, but it does the same thing.
Upvotes: 3
Views: 391
Reputation: 50358
TL;DR: this is due to the default granularity of the scheduler on Windows 10. Use timeBeginPeriod
to manually adjust the granularity of a sleeping function.
I can reproduce the effect on Windows 10 (version 21H1) on my machine. The effect happens only when usleep
is called. This comes from the OS scheduler.
Indeed, when usleep
is called, the OS causes a context switch. The threads is put in a waiting state and the console should be awaken since it was waiting for data to be printed. When the console is done, then the OS can execute other ready tasks regarding the priority of the task. A task is basically a thread of a process (typically the main one in your case). The Windows OS scheduler tends to boost the priority of tasks doing IO operation. When a task do not sleep and is not blocked on anything (eg. lock, IO reads), the task is scheduled for a predefined time called quantum (before a context-switch happens). The quantum can change from one machine to another but it is typically about a dozen of millisecond (something like 5~20 ms).
Funny point: the effect does not happen when low-level system profiler are running so the Windows scheduler change its behaviour (note the target process and the console are left unchanged). As a result, it is hard to debug the scheduler behaviour (especially since its code is closed-source).
On my system, the quantum time appears to be about 16 ms. The waiting time of an usleep
call is generally about 15 ms (it is variable and often 16 ms). This means the scheduler certainly waits for the end of a quantum for no reason by default. Note the process is not scheduled during this quantum. In fact, no core are being used.
While this strategy is beneficial to save energy, it causes applications to be less responsive. Whether this is a bug is debatable since the documentation do not give any guarantee about that. Additionally, tasks spending nearly all its time to wait for a timer may deserve to be less responsive than others by default.
The documentation of the SleepEx
function which is called by usleep
specifically states:
After the sleep interval has passed, the thread is ready to run. Note that a ready thread is not guaranteed to run immediately. Consequently, the thread will not run until some arbitrary time after the sleep interval elapses, based upon the system "tick" frequency and the load factor from other processes. The system clock "ticks" at a constant rate. To increase the accuracy of the sleep interval, call the
timeGetDevCaps
function to determine the supported minimum timer resolution and thetimeBeginPeriod
function to set the timer resolution to its minimum.
Using timeBeginPeriod(1);
solved this on my machine. The minimum resolution is 1 millisecond (in practice I get timings of 1~2 ms). By default, the scheduler was configured to wait for up to 20 minutes in the most pathological case.
Upvotes: 1