5

I got a problem with resolution of setTimeout. When I set it to 50 ms, it varies from 51 to even 80 ms. When I use sleep module, I am able to get resolution like 50 µs, so what is the setTimeout function problem to get at least 1 ms? Is there any way to fix/avoid that? The problem with sleep is that it delays everything even when callback function should be shoot, it waits... Is there an alternative solution to shoot some events in delay of exactly 50 ms?

For example with sleep module:

var start = new Date().getTime();
sleep.usleep(50);
console.log(new Date().getTime() - start);`

Result is: 0. And microtime says it is 51 to 57 µs. So what the hell?

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Flash Thunder
  • 11,672
  • 8
  • 47
  • 91
  • 1
    You might find John Resig's "[How JavaScript Timers Work](http://ejohn.org/blog/how-javascript-timers-work/)" of interest. – Jonathan Lonowski Aug 19 '13 at 17:05
  • But, even when function in `setTimeout` is only to write `new Date().getTime()` it still is totally unreliable! – Flash Thunder Aug 19 '13 at 17:27
  • 1
    The post explains the reliability, which depends on how often the event loop is idle. If it's busy with another task, the timer has to wait for that task to finish. The `timeout` value is only a *minimum*, not a guarantee. – Jonathan Lonowski Aug 19 '13 at 17:28
  • There is no way to make it more reliable? Or any other workaround? Sleep would be nice if it wouldn't use 100% of processor. – Flash Thunder Aug 19 '13 at 17:30
  • The reason it says 51 to 57 microseconds is because it takes this long (1 to 7 microseconds) for the V8 engine to process the next line of script. Just because this is one line of code to you. The usleep function likely makes some system calls, and the V8 engine takes a while to resurrect itself from making these calls and to continue to run your script. Not to mention, getting the current time itself requires system calls itself, which also takes time. – MobA11y Aug 19 '13 at 18:00

3 Answers3

2

From the setTimeout docs:

It is important to note that your callback will probably not be called in exactly delay milliseconds

The precision of the delay is determined by how little your code blocks, meaning that if you do a lot of operations on the single thread that your code has, the setTimeout might be triggered with a lot of delay. On the opposite, it will be almost exact.

You could see the difference for yourself, execute this

var start = Date.now();
setTimeout(function() { console.log(Date.now() - start); }, 500);
for(var i=0; i<999999999; ++i){}

// 1237ms

and notice the difference with this:

var start = Date.now();
setTimeout(function() { console.log(Date.now() - start); }, 500);

// 507ms
Alberto Zaccagni
  • 30,779
  • 11
  • 72
  • 106
  • For second example it is still 507ms!!!! Try... `var start = Date.now();` sleep.usleep(50); console.log(Date.now() - start);` it will give you `0` ZERO! microtime shows about `57 microseconds`. – Flash Thunder Aug 19 '13 at 17:15
  • 1
    I think this is a design "limitation". – Alberto Zaccagni Aug 19 '13 at 17:51
  • 1
    `Calling setTimeout will add a message to the queue after the time passed as second argument. If there is no other message in the queue, the message is processed right away; however, if there are messages, the setTimeout message will have to wait for other messages to be processed. For that reason the second argument indicates a minimum time and not a guaranteed time.` -[Mozilla docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/EventLoop) – user568109 Aug 20 '13 at 17:59
  • I just tried that example in the Firefox console (ubuntu 16.04 LTS), and was very surprised to see between `499` and `501` printed, so even less than the "minimum time" ! I guess that's ok in practical applications, and probably even a rounded `499.4... ms`, but I find it remarkable. In node.js it was mostly `501 ms`, sometimes `500 ms`. – kca Jun 28 '20 at 08:47
0

So, in order to acheive the best possible time rate for something like setTimeout, we want to keep the event loop for a given module as small as possible. This minimizes the amount of time the V8 engine spends doing work for the loop. Best case scenario:

setInterval(function(){}, 50);

If this is the only thing in the file, the precision will be very high, because there is nothing else going on. SO, what we do, is we fork a process for a specific module that does only the work we want to make on this interval. If the only thing within the function is an asynchronous request, it is as good as the above. So we get a very high level of precision on our setInterval function.

In one file we want the following contents, we'll call this file1.js.

var http = require('http');

var options = {
    hostname: 'localhost',
    port: 8888,
    path:'/',
    method: 'GET'
}

setInterval(function(){
    console.log('File:' + (new Date).getTime() % 50);
    //The Modulus of the time in MS should change by no more than 1 MS on consecutive runs, I confirmed this works just dandy on my system(though the first couple intervals were slower in some instances, worse I saw was a 3 MS delay)
    var req = http.request(options, function (res) {
        res.on('data', function (data) {
            console.log(data.toString());
        });
    });

    req.end();
}, 50);

In the second file we can do more work, whatever work we want actually. The important thing is we spawn a process to do the above work, contained within its OWN PROCESS. This allows that processes V8 processor to keep this event loop very small. We still are victim of the operating systems management of the given processes on a CPU, but we can still expect our file1.js module to get attention at least once every millisecond, which since all it does is an async call, followed by looking for the next time it needs to launch said async call, the most delay we get between calls is 1ms(at least that's all I saw on my system). The other file, therefore, can contain any amount of work, and one very important line:

file2.js:

var http = require('http');
var child_process = require('child_process');


http.createServer(function (req, res) {
    res.write('Hi There');
    res.end();
}).listen(8888);

///Do whatever else you need to do

child_process.fork('file1.js');//Add in this line!!!!

Expected output:

File:29
Hi There
File:29
Hi There
File:30//Notice these values only increase by 0 or 1.  
Hi There
File:31
Hi There
File:31
Hi There
File:32
Hi There
File:34//Uh oh, one went up by 2... not terrible
Hi There
File:35
Hi There
File:35
MobA11y
  • 18,425
  • 3
  • 49
  • 76
  • So no way to increase efficiency of that "looking for callbacks" functions? – Flash Thunder Aug 19 '13 at 17:19
  • Looking for callbacks? – MobA11y Aug 19 '13 at 17:23
  • Check out my example above. It is possible. This function is just screwed up. – Flash Thunder Aug 19 '13 at 17:28
  • As I said, if you're willing to busy wait, you can get more precision(likely down to where rounding will cause you to believe it is perfect), but for the sake of all programming gods in existence, your processor, your heatsink, etc... don't do this. – MobA11y Aug 19 '13 at 17:40
  • Ok, but there is a huge difference between about 10 milisecods of resolution and about 10 microseconds! – Flash Thunder Aug 19 '13 at 17:46
  • If one line of code executing precisely some number of MICROseconds after another is a requirement for your code, you should reconsider your design... not try and force a level of precision that the language/operating system is not designed to provide. – MobA11y Aug 19 '13 at 18:02
  • But usleep shows it is designed to probide such a precision. I don't really care about killing my server this way, it is 8-core, it will survive, but the problem is that when I use usleep, it uses 1 core at 100% and nothing more happens when sleeping in the script. Even PHP is much better in that. – Flash Thunder Aug 19 '13 at 18:04
  • I added an edit that explains why this is the case and re-iterate, you are trying to force Node to do something it is not designed to do. Reconsider your design, write a native extension, or go back to PHP. – MobA11y Aug 19 '13 at 18:06
  • I wrote my appliacation in C for this, but thought that maybe node.js may be quicker :/ And about example... isn't there any way to make while to skip every 1000 ticks or so? – Flash Thunder Aug 19 '13 at 18:09
  • Why don't you try explaining what you're trying to accomplish, and I can come up with a good Node alternative. Rather than trying to re-explain this extremely specific thing. Why do you need while to skip every 1000 ticks? – MobA11y Aug 19 '13 at 18:15
  • I need to run certain tasks (http request) at certain dely = 50ms. This is critical for this project. I noticed that PHP builds requests really slow, it is possible to start request every 50ms, without problem. But node.js builds requests much quicker. I made C application that does that, but seems that even the C application operating at raw sockets is slower in building query and getting answer than node.js. That's why I wanted to make in in node.js. But if there is no way to force it to shoot several http requests in 50ms delay, it is usless. – Flash Thunder Aug 19 '13 at 18:18
  • So you need some odd number of requests, and you need to spin them off every 50ms, for X number of requests? What are your precision requirements? – MobA11y Aug 19 '13 at 18:20
  • 1ms is enough. But `one` not `ten` – Flash Thunder Aug 19 '13 at 18:23
  • Also note, you can end the interval and restart it whenever you need. – MobA11y Aug 19 '13 at 18:52
  • Not sure what are u referring to :) – Flash Thunder Aug 19 '13 at 18:55
  • Why do they increase by 1 or 2? It means that it waits 51, 52ms ... and after 10 there is 4ms difference. After 100 will be 40. – Flash Thunder Aug 20 '13 at 09:36
  • For the same reason pointed out to you by everyone in this thread many times already, timers in javascript are not perfect. We can use tricks like above to get them as close as possible. – MobA11y Aug 20 '13 at 13:52
  • And that's not what it means. It means there is a 1 ms delay on 50ms each time. 50 - 101, 101 - 152, so on and so fourth. – MobA11y Aug 21 '13 at 13:21
0

Yes, I wish it would be more precise and my funny waitSort could work :)

However it works here but not in node

const posInts = [5,2,7,9,4,6,1,3]

const waitSort = arr => new Promise(done => {
  const ret = []
  const last = Math.max(...arr)
  arr.forEach(i => {
    setTimeout(() => {
      ret.push(i)
      if(i === last){
        done(ret)
      }
    }, i)
  })
})

waitSort(posInts).then(console.log)
console.log(posInts)
cstuncsik
  • 2,698
  • 2
  • 16
  • 20