March 21, 2017

Playing with Time… using JavaScript

by Krzysztof Parjaszewski

I’ve recently decided to start these series, so please expect more to come on regular basis.

Playing with Time… using JavaScript

There are couple of functions, related to time — which I’d like to remind you quickly:


getTime() — returns, the current, world time counted in milliseconds since the 1st of January 1970 00:00:00
new Date() — returns a basic Date object, the trick is — constructor sets its value to the current time from operating system
process.hrtime() / window.performance.now() — more detailed functions for receiving current time — detailed down to nanoseconds
setTimeout() and setInterval() — manipulate current application with delaying and timer operations.

In the next few tests I’ll be showing certain examples when especially setTimeout.

How I thought setTimeout should work:

Imagine the very basic function func scheduled by setTimeout at time zero. Our whole program as well as the the func function are really small — so we expect the application runtime will be idle for most of the time and when 2000ms will pass — it will be ready-steady to launch.

How setTimeout really works:

Sadly — even for such a simple application, keeping in mind my MacBook was pretty idle and has Intel i5, 2.9 GHz CPU available (one operation takes ~0.3448 nanosecond = 10-9s) — the delay was in 10-2s to 10-4s, so almost 5–7 orders of magnitude.

Ground-shocking:

simple timers test runner fiddle

Actually in my terminal using NodeJS the delay was even bigger:

// $ node
setInterval(() => {
let x = process.hrtime();
setTimeout(() => {
process.stdout.write(process.hrtime(x).toString() + '\');
}, 2000); },
500);
/* Result:
> 2,708414
2,2040334
2,2456541
2,5144434
2,567753
1,999290668
2,2768476
2,742414
2,731583
2,1619245
2,3125357
1,999806419
2,4796539
2,4191538
2,4122153
2,3216720
2,2360887
2,2978686
2,3132954
2,3731101
2,3973515
2,3004585
2,5163146
*/
view raw slowtimeout.js hosted with ❤ by GitHub

Can you see it? 2.74 seconds! Instead of 2 seconds. Mindblown!



How I thought setInterval should work:

The goal of the setInterval function is to keep on running a predefined function unlimited number of times. I imagine if my MacBook is pretty idle and the interval is about 200ms, called function func is also lightweight  —  assuming the application started at zero time, every second the func will be called 5 times, every minute 300 times etc.

Let’s try.

How setInterval really works:

I’ve created a very simple function for NodeJS:

$ node
var x = process.hrtime();
setInterval(() => {
process.stdout.write(process.hrtime(x).toString() + ", ");
var y = 2 + 3;
process.stdout.write(process.hrtime(x).toString() + ". "
+ [new Date()].map(d => d.toUTCString() + " " + d.getMilliseconds() + "\")
);
}, 200);
/** 0,202195467, 0,202341852. Tue, 21 Mar 2017 15:10:07 GMT 61
0,409969352, 0,410614804. Tue, 21 Mar 2017 15:10:07 GMT 269
0,612068498, 0,612163532. Tue, 21 Mar 2017 15:10:07 GMT 471
0,812896689, 0,812978258. Tue, 21 Mar 2017 15:10:07 GMT 672
1,15207002, 1,15324274. Tue, 21 Mar 2017 15:10:07 GMT 874
1,219037466, 1,219107891. Tue, 21 Mar 2017 15:10:08 GMT 78
1,424612498, 1,424707773. Tue, 21 Mar 2017 15:10:08 GMT 283
...
*/
view raw setIntervalTest.js hosted with ❤ by GitHub

Here are the processed results. To sum them up:

| average interval | 203.3396226 ms | 1.02% more on average |
| max interval | 210 ms | 5% more |
| sum of intervals: | 32.3310 ms | 0.5310 s |
| in theory time passed: | 31.8000 ms | |
| # times func called: | 159 times | |
view raw intervalResults.js hosted with ❤ by GitHub

So after 159 very low-complexity function calls we’re half a second after the schedule.


I don’t want to know what will happen in an app written in large-scale framework with decent complexity.



Ok, so let’s play a bit with combinations of timers:

In the real world of JavaScript application computation of our code takes some non-zero time. So we have three types of delays to take into consideration:

  • code running delays — just time spent on calculations
  • interval timer
  • timeout timer


In other words  —  uestion is: what will happen if the computation time of our function is considerably longer that the timer itself. Let’s take a deeper look on some example  —  I’ve defined a function that runs almost 2 seconds on my computer  —  for that purpose I’ve used a dummy loop with 999999999 iterations;

function almost2Seconds(counter) {
[new Date()].forEach(d => console.log(
counter.toString() + ": " + d.toUTCString()
+ d.getMilliseconds())
);
for(let i = 0; i < 999999999;i++) {
let x = i + 1 - 3;
};
[new Date()].forEach(d => console.log(
counter.toString() + ": " + d.toUTCString() + d.getMilliseconds())
);
}
/* almost2Seconds(5)
5: Tue, 21 Mar 2017 01:10:45 GMT450
5: Tue, 21 Mar 2017 01:10:47 GMT300
*/
view raw almost2Seconds.js hosted with ❤ by GitHub

Then I’ve declared a repetition of this function using a very short, 200ms intervals:

// $ node
let myCounter = 0;
setInterval(() => almost2Seconds(myCounter++), 200);
/* > 0: Tue, 21 Mar 2017 01:11:23 GMT489
0: Tue, 21 Mar 2017 01:11:25 GMT320
*/
view raw setInterval_Code.js hosted with ❤ by GitHub

I’ve also created a short table of absolute times for each execution:

deltastotal timetype
0.0000s0.0000sinit
1.8310s1.8310scode
0.2030s2.0340sinterval
1.8260s3.8600scode
0.2030s4.0630sinterval
1.8390s5.9020scode
0.2040s6.1060sinterval
1.8540s7.9600scode
0.2010s8.1610sinterval
1.9390s10.1000scode
0.2050s10.3050sinterval
1.8460s12.1510scode
0.2050s12.3560sinterval
1.8160s14.1720scode
0.2060s14.3780sinterval
1.8370s16.2150scode
0.2050s16.4200sinterval
1.8570s18.2770scode
0.2030s18.4800sinterval
1.8780s20.3580scode
0.2060s20.5640sinterval
1.8620s22.4260scode
0.2130s22.6390sinterval
1.8990s24.5380scode
view raw results.md hosted with ❤ by GitHub

The results are amazing. Instead of having a 5, 10 or 50% of delay per interval — we have almost 1000% error. What’s the reason for that? Shortly — JavaScript is an asynchronous language and when there is a heavy computation — setInterval waits for it to complete, before even thinking about applying defined intervals. We’re of course used to the 213 ms instead of 200ms thing :)


I’ve also managed to prepare an extreme example — where the 5 millisecond interval is blocked for 60 seconds :) So if you have a heavy enough computation — your timers will just get lower priority and need to wait.

Findings (EDIT — wrong, see below):

You’ve probably heard many times JavaScript is an asynchronous language. It’s even worse — not only it’s synchronous *, it’s also single-threaded. It has some notion of asynchronous mechanism, but the priority is for the main calculations, so if there’s just not enough CPU or the task is blocking for other reasons — timers won’t be delivered. You probably can have an implementation of a clock in JavaScript — that will be 30 minutes late every minute if you add some heavy animations and other calculations.

* scroll few lines below for a disclaimer

TL;DR You need to understand your tools and use them wisely.

Solutions?

Search for a good article on Web Workers. Maybe I’ll put one here.


POST-EDIT-DISCLAIMER (added 24th April 2017):

Actually — I was wrong when I wrote JavaScript is synchronous. But to fully explain this — we need to search for valid definitions, to make sure we’re all knowing what we’re talking about.
The main goal for the concurrency in programming is to run code faster and avoid waiting, using programming techniques and available hardware resources. There are 2 main strategies to run code faster:

  • asynchronous — using event-loop
  • multi-threading — using multiple call stacks

JavaScript has got a single, synchronous call stack. Browser gets blocked every time the call stack is being executed, which is why you’d like to avoid time consuming functions in there. Remember about the 60 fps principle, here’s a good article on that. Try to run this code in your console:

alert(‘Hello World’);

You’ll see browser got frozen. If you have any GIF animations — they’ll all get blocked. It happens because of the single call stack in JS. Thankfully there are more ingredients in the JavaScript runtime:

  • event-loop
  • callback-queue
  • system / Web APIs

Call stack executes its functions in a FIFO manner. Event-loop will take a look on the call stack and whenever it’s empty — it will put there a first task from the callback-queue. System / Web APIs are responsible for operations that will can done by system / browser resources, for example:

  • I/O callbacks (reading files)
  • timers (setTimeout, setInterval)
  • networking (xmlHttpRequest)

There is good video that explains this:


In the multithreading model, every thread has its own call stack and the threads are being run in parallel, so there’s no need for the event-loop. The main disadvantage for the multithreaded code is the management of the the communication between threads. It can be really difficult and requires extra code, usually a bottom-up architecture reorganization.

You can use both approaches. In order to achieve asynchronous multithreading in JS you can use Web Workers.

To sum up — there are 4 main types of approaches towards concurrency issues:

  • Single-threaded synchronous (JS call stack)
  • Single-threaded asynchronous (JS runtime)
  • Multi-threaded synchronous (for example: Clojure)
  • Multi-threaded asynchronous (JS runtime with Web Workers*)

* language features are limited in JS Web Workers (like DOM access)

Tags