C, and Shaving Milliseconds

Published 06/02/2021

I recently started learning C properly, having tinkered with the configs of various suckless programs (st, dmenu, etc) I knew a few of the fundamentals. I decided that an interval timer that could be run in the terminal would be a funny idea, mainly because of the juxtaposition between the traditional basement-dwelling Linux nerd and the most frequent use of an interval timer, for exercise.

The process of writing the code was relatively simple - C's reputation as a difficult and confusing language did not seem that real, maybe because of my previous programming experience or maybe that the simple level I was using it at was the easy part of it. In fact, I enjoyed the specificity needed over other programming languages I had previous experience in, like sh and JavaScript. For each variable I defined I had to really think about what I was using the variable for, in the case of numbers how large the numbers could be, whether it should be r/w or just readable (which presented problems later with string manipulation).

I eventually wrote the core of the program. The user would input an interval length and how many intervals to go through, a for loop would then go through each interval, with a smaller for loop inside of it to count through the seconds. The second for loop would parse a command to pass to figlet, which would then print it on the screen, clear the console and repeat. At this point, the program worked flawlessly. However, if a minute and 9 seconds were left it would pring "1:9" which was not ideal. Therefore, a few convoluted if statements later if either number was less than 10 then a "0" would be prepended in the figlet command. And so, I thought the base of the program with some extra pretty formatting was finished. That evening, I left it running as a test to make sure the if and other formatting statements weren't going to affect the overall time too much. I ran another interval timer at the same time, and I noticed that mine slowly drifted off the other one, by roughly 1s/m.

Sure enough, as the program finished I checked the result of the time command I ran with it - 30:30.36, when it should have taken 30:00.00 flat. After some further testing, I found that per minute, the program drifted roughly 0.86 seconds. I first tried fiddling with different optimisations, slightly cleaning up the code, but it quickly became apparent that without removing the formatting code, I would not be able to get it back on time. I had forgotten one thing - I was artificially inflating the time to run the code already, with a sleep(1). After some quick googling, I found the almighty clock_t function. Setting it up around the contents of the for loop, I could now measure how long the formatting took. The only barrier was that sleep only accepts integer values, so I had to either use the deprecated usleep or the extremely confusing nanosleep. After far too much time experimenting with both, I bit the bullet and just used usleep. Even though I now got a warning whenever I compiled, the sleep time was now reduced to account for the formatting time. Since the maths for substracting the time obviously could not be included in the clock_t, there was still a small delay - but now it was roughly 0.34s/m.M

I'll run a proper test at some point, but now over 30 minutes the overall time should be ~30:15.00, which I think is as close as I'm going to get it and especially if you are using it for exercise, the 15 extra seconds won't hurt.