Types and Programming Languages Part 5 – Sleeping and measuring time

This is the fifth part of the Types and Programming Languages series. For your convenience you can find other parts in the table of contents in Part 1 — Do not return in finally

How do you sleep in your application? There are multiple solutions, most of the times similar to Thread.Sleep which synchronously blocks the thread or Thread.SleepAsync which returns a promise to indicate when the given time passed.

What they have in common is they are based on the wall clock. And while it makes sense and is simple to understand, we need to keep in mind it’s not the only “sleeping” we can imagine. Instead of sleeping for a “real time passing by” we may want to sleep for “time consumed by our application”. Is there a difference? Let’s take this C# code:

We create a thread which does some busy loop and then sleeps physically for a millisecond. In the main thread we measure not only the wall clock time but also the time consumed by all threads in the application. Sample results:

You can see that we waited for a second but actual work time was around 300 milliseconds. Can we do better?

We can, let’s take this function:

So we take current times and then sleep for long enough (possibly many times). Output:

This time you can see that we physically slept for 2 seconds while our threads worked for around 1016 milliseconds.

And this is just the beginning — we used only one worker thread. What if we create many of them?

So this time we slept for a physical second but we actually did 4 seconds of work. Let’s try this one:

This time we divide the time by threads count to “not oversleep”. However, we get this:

So we worked for 1300 thread milliseconds but actually 0 seconds of the wall clock time!

And here we come to another important thing: it’s hard to measure time with high precision. Method I presented above relies on GetThreadTimes function which is known for low precision. There are other solutions like QueryThreadCycleTime based on QueryPerformanceCounter. And it’s important to understand that these things are very hardware dependent.

However, there is yet another issue — what does it mean to “measure the time” for a multithreaded algorithm? Are we interested in the “wall clock” time from start till the end or maybe the actual “threads time” spent on working? And these methods give different results.

It’s no surprise that benchmarking libraries do a lot of black magic, like measuring CPU frequency (which changes over time) increasing the frequency forcefully (by running empty loops), or just execute the same thing many times to make sure noise is minimized. It’s not simple.