process.nextTick performance question

678 views
Skip to first unread message

Tristan

unread,
Dec 21, 2011, 5:53:45 PM12/21/11
to nod...@googlegroups.com
I was exploring actor-like asynchronous message passing in Node.js and came across a result that surprised me.

Setting aside the merits of message passing, etc, the surprise was the performance difference between delegating a function to be executed via process.nextTick versus being executed immediately.

I've read here before that process.nextTick is "really" fast, but it clearly has to do a lot more work than a simple loop ( that seems obvious in retrospect ).

My quick benchmark here: https://gist.github.com/1507721

Results on my machine usually are something like:
loop result: 52ms
nextTick result: 1108ms

Why is it so much slower? 

I took a look here: https://github.com/joyent/node/blob/master/src/node.js#L181 and found process._needTickCallback() ( but couldn't find that function in EventEmitter which is used as process prototype it seems https://github.com/joyent/node/blob/master/src/node.js#L34 )

From this discussion https://groups.google.com/d/topic/nodejs/qluCAFK5zp4/discussion it appears that by calling process.nextTick I am handing over control back to Node ( via _needTickCallback() I assume ). From that I am guessing the extra cost comes from the back and forth between V8 and Node?  ( I'm not sure I make any sense here )

From here https://groups.google.com/d/topic/nodejs/vNxWIz5z2Y0/discussion mscdex provides a concise explanation:

Well, think of it roughly as an optimized version of this: 

while(stuffLeftToDo) { 
  checkCurrentTickTasks(); 
  checkIOEvents(); 


When you do process.nextTick() in your code, it'll add the supplied 
function to a queue to be executed at the beginning of the next 
iteration of the event loop (after any I/O events have occurred in the 
current tick). When the IO events are finished, the next iteration/ 
tick begins (if there are still things to do). Node first checks if 
there are any functions queued to be executed, and executes those in 
order. It then checks for I/O events, and repeats the process.
Which seems to be the V8 to Node back and forth again? ( again, not sure I'm making sense )

I'm not really familiar with the node codebase, can someone point me in the right direction where to look for implementation of _needTickCallback() or give me a birds eye view at what's happening there? 

Cheers,

Tristan

Ben Noordhuis

unread,
Dec 21, 2011, 6:07:08 PM12/21/11
to nod...@googlegroups.com

It's in src/node.js and src/node.cc (there a JS component plus some
C++ bindings).

The reason process.nextTick() is slower than a direct loop is (mostly) twofold.

1) V8 knows impressively well how to optimize loops, closures less so.
Your benchmark creates one million closures. That's going to hurt.

2) process.nextTick() defers your callback to the next tick. In
between, everything else runs - other callbacks in your application,
Node's under-the-hood machinery. There will be a delay even if it's
only microseconds.

3) process.nextTick() itself could be optimized more. I did some work
on that a while ago but got distracted by other things.

With your particular example, #1 is the great performance killer.

Tristan

unread,
Dec 21, 2011, 6:35:23 PM12/21/11
to nod...@googlegroups.com
Wow! #1 was a huge difference.

I thought I took care of closures by generating them both in the loop and process.nextTick, but removing them per your response made a significant difference. This is much more along the lines of what I expected. 

Results using benchmark without closures:
loop result: 22ms
nextTick result: 178ms

Benchmark without closures : https://gist.github.com/1508208

Thanks Ben, that's a much more encouraging result.

fent

unread,
Dec 21, 2011, 11:04:30 PM12/21/11
to nodejs
You are comparing two very different things here. process.nextTick()
defers the given function, that means it will not execute it until the
Javascript interpreter reaches the end of the current code stack it's
currently executing. In your above test, loopTest() executes instantly
while nextTickTest() exeucutes later after the loop.

When people say process.nextTick() is faster, they are comparing it to
the common alternative of doing it in the browser which would be to do
setTimeout(func, 0)

https://gist.github.com/1508863

setTimeout result: 6156ms
nextTick result: 196ms

Tristan

unread,
Dec 21, 2011, 11:33:34 PM12/21/11
to nod...@googlegroups.com
Thanks fent, that makes sense why "faster" is used in reference to process.nextTick(). 

I did want to measure the overhead of process.nextTick() as compared to instant execution, however. 

( tl; dr):
I was looking at doing async computation on top of async i/o provided by Node.js ( actor-like asynchronous message passing ) so the benchmark was useful to me.

( longer justification for why I was doing that ):
I'm interested in the actor model of computation where pretty much instead of function calls, messages are being passed around. The first ( naive ) experiment was to have each message use process.nextTick() for delivery, hence the benchmarks. However, seeing the result and learning from some code I've been reading, it seems a much more effective approach will be to batch a number of messages to deliver per tick, and only then execute process.nextTick(). That way the overhead of process.nextTick() ( as compared to instant execution ), is amortized, while maintaining responsiveness to interrupts.

Jorge

unread,
Dec 22, 2011, 3:25:27 AM12/22/11
to nod...@googlegroups.com

Do you realize that all the nextTicks in your test are running at the same tick/event loop turn ?
--
Jorge.

Tristan

unread,
Dec 22, 2011, 3:32:52 AM12/22/11
to nod...@googlegroups.com
I do now. Thanks for pointing it out. I'm gonna have to mess with it a little more.

Wan Li

unread,
Dec 22, 2011, 5:52:24 AM12/22/11
to nod...@googlegroups.com
On Thu, Dec 22, 2011 at 12:04 PM, fent <rol...@gmail.com> wrote:
You are comparing two very different things here. process.nextTick()
defers the given function, that means it will not execute it until the
Javascript interpreter reaches the end of the current code stack it's
currently executing. In your above test, loopTest() executes instantly
while nextTickTest() exeucutes later after the loop.

When people say process.nextTick() is faster, they are comparing it to
the common alternative of doing it in the browser which would be to do
setTimeout(func, 0)

setTimeout(func, 0)
is equivalent to setTimeout(func, 4) in modern browser and setTimeout(func, 10) in none IE6 like browsers.
4ms * 1000000  = 4s


https://gist.github.com/1508863

setTimeout result: 6156ms
nextTick result: 196ms

On Dec 21, 4:35 pm, Tristan <tristan.slomin...@gmail.com> wrote:
> Wow! #1 was a huge difference.
>
> I thought I took care of closures by generating them both in the loop and
> process.nextTick, but removing them per your response made a significant
> difference. This is much more along the lines of what I expected.
>
> Results using benchmark without closures:
> loop result: 22ms
> nextTick result: 178ms
>
> Benchmark without closures :https://gist.github.com/1508208
>
> Thanks Ben, that's a much more encouraging result.

--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
You received this message because you are subscribed to the Google
Groups "nodejs" group.
To post to this group, send email to nod...@googlegroups.com
To unsubscribe from this group, send email to
nodejs+un...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/nodejs?hl=en?hl=en



--
>: ~

Matt

unread,
Dec 22, 2011, 5:57:15 AM12/22/11
to nod...@googlegroups.com
Interesting - I use closures a lot... Do you know if they are working on this?

Diogo Resende

unread,
Dec 22, 2011, 5:57:21 AM12/22/11
to nod...@googlegroups.com
On Thu, 22 Dec 2011 18:52:24 +0800, Wan Li wrote:
> setTimeout(func, 0)
> is equivalent to setTimeout(func, 4) in modern browser and
> setTimeout(func, 10) in none IE6 like browsers.
> 4ms * 1000000  = 4s

Uh?

Did you mean:

4µs * 1000000 = 4s

Or:

4ms * 1000 = 4s

?

---
Diogo R.

Wan Li

unread,
Dec 22, 2011, 7:12:07 AM12/22/11
to nod...@googlegroups.com

sorry,I'm wrong

Tristan

unread,
Dec 22, 2011, 11:13:56 AM12/22/11
to nod...@googlegroups.com
As Jorge pointed out, all the nextTicks in my benchmark were getting dumped into the second tick/event loop turn, so the nextTick overhead came into play only once.  Here's a corrected benchmark without closures and it is very much along the same lines as the "with closures" benchmark. 

Corrected benchmark without closures: https://gist.github.com/1510817

Results:
loop result: 30ms
nextTick result: 1103ms

It seems that closure optimization was not the reason for poor performance, instead much more likely is Ben's point #2 ( all the under the hood Node stuff ).

Liam

unread,
Dec 22, 2011, 1:36:40 PM12/22/11
to nodejs
There was a long thread on the dev list a while back about closure
overhead. It's measurably slower than calling a pre-defined function,
but tiny (11 nanoseconds on my Core 2 Duo 2.2Ghz).

It seems that your benchmark shows the event loop taking about 1.1us
per iteration.

Jorge

unread,
Dec 22, 2011, 5:01:45 PM12/22/11
to nod...@googlegroups.com

Even in total absence of any events, calling a function in a while/for loop is always going to be much faster than looping via process.nextTick()s.

According to your benchmark it's only ~ 36 times faster, but the difference is even bigger than that, it seems to me.

Calling a function in a while (i--) { f() } loop, I get close to a hundred million calls per second, but only a mere half a million via nextTick()s. That's almost 200x faster.

Here's my benchmark: https://gist.github.com/1511972 and this is what I get in my Mac:

$ node nextTick.js
loopsPerSecond: 95419847.3, nextTicksPerSecond: 523953.4, ratio: 182.1x times faster
loopsPerSecond: 95785440.6, nextTicksPerSecond: 522615.9, ratio: 183.3x times faster
loopsPerSecond: 95969289.8, nextTicksPerSecond: 525575.9, ratio: 182.6x times faster
...
--
Jorge.

Tristan

unread,
Dec 22, 2011, 5:22:19 PM12/22/11
to nod...@googlegroups.com
On my Mac for comparison:

loopsPerSecond: 63897763.6, nextTicksPerSecond: 868994.7, ratio: 73.5x times faster
loopsPerSecond: 61199510.4, nextTicksPerSecond: 910923.3, ratio: 67.2x times faster
loopsPerSecond: 63572790.8, nextTicksPerSecond: 921926.8, ratio: 69.0x times faster

the conclusion I draw from that is that I need a new Mac

Tristan

unread,
Dec 22, 2011, 5:29:41 PM12/22/11
to nod...@googlegroups.com
Huh... this is interesting, I was wondering why I crammed in more nextTicks than you.

node v0.4.11-pre
loopsPerSecond: 62266500.6, nextTicksPerSecond: 925533.3, ratio: 67.3x times faster
loopsPerSecond: 65274151.4, nextTicksPerSecond: 877635.6, ratio: 74.4x times faster
loopsPerSecond: 65746219.6, nextTicksPerSecond: 946511.2, ratio: 69.5x times faster

node v0.6.5
loopsPerSecond: 83892617.4, nextTicksPerSecond: 500132.2, ratio: 167.7x times faster
loopsPerSecond: 83892617.4, nextTicksPerSecond: 515444.3, ratio: 162.8x times faster
loopsPerSecond: 83752093.8, nextTicksPerSecond: 495376.5, ratio: 169.1x times faster

node v0.6.6
loopsPerSecond: 83963056.3, nextTicksPerSecond: 568846.9, ratio: 147.6x times faster
loopsPerSecond: 83056478.4, nextTicksPerSecond: 611906.8, ratio: 135.7x times faster
loopsPerSecond: 83682008.4, nextTicksPerSecond: 563709.4, ratio: 148.4x times faster

Jorge

unread,
Dec 22, 2011, 5:30:40 PM12/22/11
to nod...@googlegroups.com

LOL. You get a *much* better nextTick rate !

What node was that ?

$ node -v
v0.6.6
--
Jorge.

Jorge

unread,
Dec 22, 2011, 6:22:27 PM12/22/11
to nod...@googlegroups.com
On 22/12/2011, at 23:29, Tristan wrote:

> Huh... this is interesting, I was wondering why I crammed in more nextTicks than you.
>
> node v0.4.11-pre
> loopsPerSecond: 62266500.6, nextTicksPerSecond: 925533.3, ratio: 67.3x times faster

> node v0.6.6


> loopsPerSecond: 83963056.3, nextTicksPerSecond: 568846.9, ratio: 147.6x times faster

The nextTick() rate -almost- halved, really ?
--
Jorge.

Liam

unread,
Dec 22, 2011, 8:13:17 PM12/22/11
to nodejs
Hmm the event loop seems to be significantly slower in 0.6 vs 0.4

ryan

unread,
Dec 22, 2011, 8:46:47 PM12/22/11
to nod...@googlegroups.com
looking into it...

Liam

unread,
Dec 29, 2011, 3:52:34 AM12/29/11
to nodejs
Any update on this?

Dmitry Eroshenko

unread,
Jan 11, 2012, 8:55:22 PM1/11/12
to nod...@googlegroups.com
My results:

loop result: 69ms
nextTick result: 1214ms

and

setTimeout result: 14240ms

I think the idea that "nextTick" faster comparing with "setTimeout", not just synchronous code. 
When you need to call some function which will do I/O (write socket for example) you can do it with "nextTick", 
then code below will continue executing while in function which you put in nextTick you can wait while it gets content of some page via HTTP (so, that is I/O operation).
nextTick - fast replacement for setTimeout(function(){}, 0).


Reply all
Reply to author
Forward
0 new messages