Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timers: improve linked list performance #8961

Closed
wants to merge 1 commit into from
Closed

Conversation

Trott
Copy link
Member

@Trott Trott commented Oct 7, 2016

Checklist
  • make -j8 test (UNIX), or vcbuild test nosign (Windows) passes
  • commit message follows commit guidelines
Affected core subsystem(s)

timers

Description of change

By moving the linked list manipulations out of a separate module and
into lib/timers.js, a significant performance increase is seen in the
timer benchamrks. I am seeing consistent 50% improvement with a p-value
of .0005 for the "breadth" benchmark with "thousands" set to 5.

This is probably due to removing the overhead of calling an external
function and possibly also removing some special handling that is not
needed in some cases.

@Trott Trott added the timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout. label Oct 7, 2016
@Trott
Copy link
Member Author

Trott commented Oct 7, 2016

Benchmark results:

                                             improvement significant      p.value
 timers/timers.js type="breadth" thousands=5     50.57 %         *** 0.0005320786
 timers/timers.js type="depth" thousands=5        0.29 %             0.1920428012

(Note that I reduced the number of timers to 5K from 500K. Worth running on 500K timers too, perhaps.)

@jasnell
Copy link
Member

jasnell commented Oct 7, 2016

ping @mscdex

@ronkorving
Copy link
Contributor

But you're now replacing public (linkedlist) API with private property manipulation. Do we wanna go here or are there other ways to optimize? Also, do we need this optimized further? (what are the real-world benefits?)

@Trott
Copy link
Member Author

Trott commented Oct 7, 2016

But you're now replacing public (linkedlist) API with private property manipulation.

The public API is deprecated. I don't think it's a big deal to not use it.

As for real-world use cases, that's an excellent question that I hope someone else can shed light on. The benchmark we've had for years tests 500K timers, which seems like more than I would ever expect to really see, but time and again reality shows my imagination to be inadequate, so I don't know.

I guess now is a good time to /cc @Fishrock123

@ronkorving
Copy link
Contributor

@Trott I only meant to say: public to internals. We don't enjoy internals messing with an EventEmitter's internals either for the same reason, right? We know we can control it, but it's a bit hackish. I'll leave it there though :) Hoping to get some insight on the other questions.

@Fishrock123 Fishrock123 self-assigned this Oct 7, 2016
@Trott
Copy link
Member Author

Trott commented Oct 7, 2016

Bumping the benchmark to 10K:

                                              improvement significant      p.value
 timers/timers.js type="breadth" thousands=10     26.58 %         *** 4.359489e-22
 timers/timers.js type="depth" thousands=10        0.08 %             1.473518e-01

@mscdex
Copy link
Contributor

mscdex commented Oct 7, 2016

I've seen instances of similar things happening (especially so with V8 5.4 now) because of how the functions are compiled (TurboFan vs. Crankshaft), if they're inlined, etc.

@Fishrock123
Copy link
Contributor

The "breadth" benchmark tests it with passing lots of arguments, right? I'm not really sure how this could effect that or how important that is.

Also, there are two changes here: removing unnecessary asserts, and then moving the list logic.
I would recommend splitting those and checking separately.

Additionally I would feel a lot better if we didn't rely on just the benchmark output here but also took a look at V8 performance profiles from the benchmarks by using --prof.

@Fishrock123
Copy link
Contributor

The public API is deprecated. I don't think it's a big deal to not use it.

We can not use it but I think we are probably stuck replicating it...

@Trott
Copy link
Member Author

Trott commented Oct 7, 2016

Effect is smaller but still statistically significant with the default 500K timers.

 timers/timers.js type="breadth" thousands=500      3.27 %         *** 0.0001944684
 timers/timers.js type="depth" thousands=500       -0.80 %             0.4706588160

I wonder if we should reduce that benchmark to default to, say, 5K and 10K. It seems like 500K is more of a pummel test (will this run successfully after 20 minutes or crash?) and I wonder if the performance when the data is that large is more about the hardware and less about what Node.js does, so things that are hugely positive get watered down.

@Fishrock123
Copy link
Contributor

Fishrock123 commented Oct 7, 2016

I'd say try 100k -- anywhere below probably doesn't even matter for this fine grain of perf.

(if you're trying that on a light laptop.. maybe we should try on a machine with better perf...)

By moving the linked list manipulations out of a separate module and
into `lib/timers.js`, a significant performance increase is seen in the
timer benchamrks. I am seeing consistent 50% improvement with a p-value
of .0005 for the "breadth" benchmark with "thousands" set to 5.

This is probably due to removing the overhead of calling an external
function and possibly also removing some special handling that is not
needed in some cases.
@Trott
Copy link
Member Author

Trott commented Oct 7, 2016

Oh, wait, the breadth one is fast, it's the depth one that takes forever. So I can do 500K and just run the breadth test no problem. (I guess that's kind of what @ronkorving may have been alluding to indirectly--that this is already fast, does it really need to be faster in any real-world use cases? I don't know the answer to that.)

So, here's 500K running the breadth test only:

                                               improvement significant     p.value
 timers/timers.js type="breadth" thousands=500      9.58 %         *** 1.19363e-05

And the raw CSV generated:

"binary", "filename", "configuration", "rate", "time"
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3396.354479446149, 0.147216671
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3687.6264445622046, 0.135588571
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3338.3412012248987, 0.149774984
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3692.0583499990385, 0.135425812
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3250.9813379756997, 0.153799714
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3651.1511703571564, 0.136943111
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3407.891664269044, 0.146718279
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3634.5109080831317, 0.137570092
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3392.8350191493646, 0.147369382
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3643.730066063741, 0.13722202
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3344.6191038729926, 0.149493854
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3516.4306911932736, 0.142189636
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3188.02656794224, 0.156836836
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3564.665128584957, 0.14026563
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3310.240258694482, 0.151046438
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3570.151094863591, 0.140050095
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3319.8972003127633, 0.150607073
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3288.355859557478, 0.15205167
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3101.9539555384777, 0.161188724
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3611.314404036629, 0.138453744
"old", "timers/timers.js", "type=""breadth"" thousands=500", 3209.4505326330286, 0.155789907
"new", "timers/timers.js", "type=""breadth"" thousands=500", 3487.280176940132, 0.143378213
"old", "timers/timers.js", "type=""breadth"" thousands=500", 2909.9764731312125, 0.171822695

@Trott
Copy link
Member Author

Trott commented Oct 7, 2016

I should note that the two 500K benchmarks were run on different laptops. That may explain why one saw a larger benefit than the other. Regardless, both results have very low p-values (which is what you want--I'm sure someone will correct me on this because I'm being imprecise, but low p-value more or less is taken to equate to statistically significance).

@Fishrock123
Copy link
Contributor

All TCP connections use timers. The way I last checked on the significance on impact in context was to make an http server with --prof, and then pummel it with wrk (and I mean really stress it).

@Trott Trott closed this Oct 13, 2016
@ronkorving
Copy link
Contributor

Closed?

@Trott
Copy link
Member Author

Trott commented Oct 13, 2016

@ronkorving Yeah, I wasn't going to get around to benchmarking it any time soon, and I'm not sure if it will show a statistically significant benefit or not. timers are something to be careful with, so since I'm not going to give it the attention it deserves any time soon, I closed it.

I'm happy to re-open it if someone else wants to take on benchmarking it (per @Fishrock123's comment) and/or I'm happy to have someone enthusiastic about it take it on in their own pull request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
timers Issues and PRs related to the timers subsystem / setImmediate, setInterval, setTimeout.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants