Skip to content
This repository was archived by the owner on Nov 28, 2020. It is now read-only.
This repository was archived by the owner on Nov 28, 2020. It is now read-only.

Performance impact of async_hooks #181

Description

@bmeurer

I've already started a related discussions on Twitter earlier here, but Twitter doesn't scale for this kind of discussion, so I thought I should move it here, so it's easier to follow the discussion. For background: @jasnell approached me at NodeConfEU in November 2017, asking for help on the performance of async_hooks on the V8 side, especially when it comes to the Promise lifecycle hooks. And I've talked briefly with @thlorenz about it during the conference (although I have to admit that I was using the wrong terminology and thus kinda ruined the conversation, sorry). And we had chatted briefly about this as well with @trevnorris during one of the CTC-V8 calls.

Since Promise based APIs are likely becoming a (big) thing for Node 10 and at the same time there are estimates (i.e. by @ofrobots) that by the end of next year every Node server in enterprise is going to run with async_hooks, we should start a discussion about this early one to stay ahead of the problem before it becomes a real problem for our users.

In the last year @gsathya and @caitp already spent a lot of effort optimizing promises in V8 in general, so we're in a pretty good position wrt. baseline performance!

I'd like to use this forum to come to an agreement on what the concrete use cases are (at least estimate that), which aspects of the performance matter the most (i.e. promises created inside of C++ vs. in JS land), and what are useful benchmarks to drive the performance work in a meaningful way. I think we need both a set of micro-benchmarks to drive certain, in addition to real-world code that makes heavy use of promises, i.e. koa.js or fastify servers maybe? I feel that these benchmarks are most important, otherwise we might easily sink a lot of effort into work that doesn't help real-world use cases in the end. That's why I opened the issue on the benchmarking WG.

Comments and contributions are very welcome!

Activity

  1. self-assigned this
    on Dec 16, 2017
  2. bmeurer commented on Dec 16, 2017

    @bmeurer
    MemberAuthor

    There's been a related discussion in nodejs/promises#31 earlier this year, which yielded a couple of interesting improvements and insights.

    Pinging @nodejs/performance @nodejs/async_hooks

  3. benjamingr commented on Dec 16, 2017

    @benjamingr
    Member

    I'd like to use this forum to come to an agreement on what the concrete use cases are (at least estimate that), which aspects of the performance matter the most (i.e. promises created inside of C++ vs. in JS land), and what are useful benchmarks to drive the performance work in a meaningful way.

    Particularly about async_hooks and promises or promises in general?

    For async_hooks and promises - I think a server-application benchmark could be good here - with contexts through async functions through several layers of calls - I'd then listen to promiseResolve and write interesting things about the app while sending it "user data" and measure how long it takes. "interesting things" can be things like how many times the database is hit, request latency for different request stages etc.

    For promises in general there are at least 2-3 good ideas in that thread that can get native promises finally faster than userland alternatives like bluebird. Namely skilling the microtask queue when possible and inlining calls - not to mention async iterators that aren't fast yet but could revolutionize programming in Node.js if they were.

  4. bmeurer commented on Dec 17, 2017

    @bmeurer
    MemberAuthor

    I'd like to use this forum to come to an agreement on what the concrete use cases are (at least estimate that), which aspects of the performance matter the most (i.e. promises created inside of C++ vs. in JS land), and what are useful benchmarks to drive the performance work in a meaningful way.

    Particularly about async_hooks and promises or promises in general?

    Improving promise performance in general is also a good idea, but this particular issue is primarily about the impact of async_hooks, i.e. about the performance cliff that you fall off when you enable async_hooks.

    As for promises in general and in particular async/await and async iterators, we will need good benchmarks as well to drive meaningful performance work. I've started to collect some ideas for promise performance improvements, but I don't feel like it's a good approach to measure our success in terms of the bluebird benchmarks.

    I think @mcollina had some comments about that before. Can we derive a simple representative performance test from fastify to get things going?

  5. AndreasMadsen commented on Dec 17, 2017

    @AndreasMadsen
    Member

    Specifically, regarding PromiseHooks and performance I would like to see:

    kDestroy is added. I think in many cases it is straightforward to know when a promise can no longer be refered too. Thus we could emit the destroy hook much sooner, in other cases it would be fine to wait for garbage collection, but if V8 could instrument that for us directly it would be great.

    I've gotten many real-life complains from both APM providers and other async_hooks users that they experience a "memory leak". The reality is, that there is no memory leak but that it unintuitive that the destroy event is not emitted as soon as possible but rather at garbage collection.

    I'm guessing this would improve performance a lot, as we would no longer have to wrap the promise object itself to get notified about garbage collection. Essentially we could just create an unreferenced basic object on kInit and set then just set the asyncId and triggerAsyncId (two doubles) on the internal fields.


    Extra feature requests that are not performance related.

    /cc @addaleax

  6. bmeurer commented on Dec 18, 2017

    @bmeurer
    MemberAuthor

    @AndreasMadsen Thanks for the feedback, that's very interesting. These seem to be more on the feature request side.

    I think in many cases it is straightforward to know when a promise can no longer be refered too.

    Do you have a suggestion/intuition here how to do that?

    I'm guessing this would improve performance a lot, as we would no longer have to wrap the promise object itself to get notified about garbage collection. Essentially we could just create an unreferenced basic object on kInit and set then just set the asyncId and triggerAsyncId (two doubles) on the internal fields.

    So you're saying that by changing the API and adding machinery to implement kDestroy on the V8 side we should be able to improve performance significantly?

  7. bmeurer commented on Dec 18, 2017

    @bmeurer
    MemberAuthor
  8. AndreasMadsen commented on Dec 18, 2017

    @AndreasMadsen
    Member

    Do you have a suggestion/intuition here how to do that?

    Often promises are only "referenced" once, either by .then() chaining or they are returned and used in await. At least for the user, it is quite obvious that the promise can't be referenced after that.

    function wait(ret, ms) {
      // hint: the promise is directly returned
      return new Promise(function (resolve, reject) {
        setTimeout(() => resolve(ret), ms);
      });
    }
    
    function main() {
     // hint: the promise is directly used in `await` without being assigned to a variable
     await wait(1, 10) // after this line the user expects the `destroy` hook to emit
     await wait(2, 10) // same for this promise
     await wait(3, 10) // same for this promise
    }

    So you're saying that by changing the API and adding machinery to implement kDestroy on the V8 side we should be able to improve performance significantly?

    Yes, that would be my guess. Wrapping and unwrapping the promise object is definitely the most expensive part of our PromiseHooks integration. Not having to do that, should improve performance.

  9. bmeurer commented on Dec 18, 2017

    @bmeurer
    MemberAuthor
  10. benjamingr commented on Dec 18, 2017

    @benjamingr
    Member

    Often promises are only "referenced" once, either by .then() chaining or they are returned and used in await. At least for the user, it is quite obvious that the promise can't be referenced after that.

    If V8 could detect that with escape analysis and optimize the return value of that function to an entirely different implementation that would be awesome. Fast libraries all hack this by having the single listener case optimized - but an engine that can prove it and avoid allocating extra stuff would be awesome. In terms of promise hooks I suspect it would also simplify things a lot for the common case.

  11. mcollina commented on Dec 18, 2017

    @mcollina
    SponsorMember

    On the benchmarks side, we can also consider Hapi v17, which is completely async-await based.

    The problem with real-world code is that it typically involves a database, and that makes it very hard to measure small improvements. IMHO we should measure:

    1. receive a request, performs a query in the DB, returns value as JSON
    2. receive a request, performs a query in the DB, performs another query in the DB, returns a JSON
    3. receive a request, performs a query in the DB, send an HTTP request somewhere (maybe using fetch), returns a JSON

    I fear this would have to be a synthetic application.

  12. benjamingr commented on Dec 18, 2017

    @benjamingr
    Member

    I fear this would have to be a synthetic application.

    This is what Doxbee did (the bluebird benchmark suite) which you're familiar with - we can fork and extend it.

  13. mcollina commented on Dec 18, 2017

    @mcollina
    SponsorMember

    @benjamingr I think we are all more concern on overall impact of async await rather than a comparative measurement between implementations. Plus, I think we should focus on overall performance including a web framework, a database and HTTP requests rather than just the promise/async await layer.

  14. 26 remaining items

  15. bmeurer commented on Jan 18, 2018

    @bmeurer
    MemberAuthor

    Accidentally commented on the TSC thread instead of this one. So for future reference, I also did a test run of simple hapi and koa servers (using @mcollina's autocannon tester), again with and without async_hooks enabled to get more real-worldish numbers (the Promise benchmarks arguably really stress promises pretty heavily). The results were pretty interesting (with latest Node 9.4.0):

    Results for Node 9.4.0

    The koa test is super flaky, so the performance difference could also be noise, but for hapi, which makes heavy use of async/await, there's pretty consistent 30% performance drop with just an empty init hook. See bmeurer/async-hooks-performance-impact for the benchmarks and additional information.

    And to provide even more data for the discussion: The performance drop also increases with the number of hooks being used (maybe not unsurprising). For example for the Promise benchmarks, we see additional performance regressions:

  16. ruimarinho commented on Jan 18, 2018

    @ruimarinho

    Very interesting data @bmeurer, thanks for the insights on this subject. We definitely hit the with async_hooks (all) scenario since the current domains implementation on top of async_hooks registers all handlers. That's more in line with what we've seen in production.

    async/await though will always use native promises.

    Does this hold true even even when await Promise.all([p1, p2]) where const Promise = require('bluebird')?

  17. bmeurer commented on Jan 19, 2018

    @bmeurer
    MemberAuthor

    Does this hold true even even when await Promise.all([p1, p2]) where const Promise = require('bluebird')?

    In that case you'll probably use bluebird promises for the Promise.all and native promises for the await, which I guess is not what you want. The ES2017 spec forces await and async function to always use native promises (which I consider a good thing FWIW). I think @gsathya brought this up earlier.

  18. holyjak commented on Nov 1, 2018

    @holyjak

    Let me know whether it is not relevant and I should delete my post, but I experienced quite a bad increase in CPU usage when I activated async_hooks (Node 8 and 11) - see https://theholyjava.wordpress.com/2018/11/01/beware-the-performance-cost-of-async_hooks-node-8/ for details.

  19. mcollina commented on Nov 1, 2018

    @mcollina
    SponsorMember

    I would recommend to check out the latest Node 8, Node 10 and Node 11, as the runtimes that are used in the blog post are quite old, or not supported anymore (Node 9). There should have been some improvement in the area.

    Overall async_hooks  are very costly, especially with promises.

  20. holyjak commented on Nov 1, 2018

    @holyjak
  21. dnutels commented on May 9, 2019

    @dnutels

    Tried it on Node 12.2 on Windows 10 U. Still costly.

    regular Bluebird-doxbee: 126 ms.
    init Bluebird-doxbee: 551 ms.
    full Bluebird-doxbee: 707 ms.
    regular Bluebird-parallel: 170 ms.
    init Bluebird-parallel: 1048 ms.
    full Bluebird-parallel: 1316 ms.
    regular Wikipedia: 334 ms.
    init Wikipedia: 2129 ms.
    full Wikipedia: 2621 ms.
    regular hapiserver: 5477.2 reqs.
    init hapiserver: 3266.6 reqs.
    full hapiserver: 2727.3 reqs.
    regular koaserver: 8231.8 reqs.
    init koaserver: 7309.6 reqs.
    full koaserver: 6771.1 reqs.
    
  22. omeraha commented on Jul 29, 2019

    @omeraha

    I would recommend to check out the latest Node 8, Node 10 and Node 11, as the runtimes that are used in the blog post are quite old, or not supported anymore (Node 9). There should have been some improvement in the area.

    Overall async_hooks  are very costly, especially with promises.

    @mcollina
    Can you shortly explain the reasons for the substantial performance impact?

  23. alekbarszczewski commented on Sep 23, 2019

    @alekbarszczewski

    Is it possible to reduce performance impact of async_hooks in the future (in future Node.js versions)? Or the performance will be always so bad because of the nature of async_hooks and how they work?

  24. benjamingr commented on Sep 24, 2019

    @benjamingr
    Member

    @alekbarszczewski it is mostly blocked on people doing the product work and suggesting concrete improvements.

  25. rochdev commented on Oct 2, 2019

    @rochdev

    If anyone can provide pointers of where to start to try and fix this, and/or a tl;dr of why it's so slow, I can try to look into it.

  26. mhdawson commented on Oct 3, 2019

    @mhdawson
    Member

    @rochdev how much time will you have to invest? It will be a relatively big ramp up.

  27. mhdawson commented on Oct 3, 2019

    @mhdawson
    Member

    @rochdev my suggestion is to come to the next https://github.com/nodejs/diagnostics meeting. We are looking for a Champion to help with moving async hooks forward.

    As per the calendar the next meeting is scheduled for Oct 9 (next wed) and an issue will be opened in the repo next Monday with the meeting links etc.

  28. rochdev commented on Oct 3, 2019

    @rochdev

    I definitely don't expect that it will be an easy task, but it's definitely an issue that is severely impacting APM vendors. I don't know if I'll be able to put a lot of time into it, at least short term, but I'd like to familiarize myself with the issue and how async_hooks currently works in general.

    I'll try to join the next Diagnostics meeting. Thanks for the tip!

  29. rochdev commented on Oct 7, 2019

    @rochdev

    I won't be able to make it to this week WG meeting. I'll make sure I stay available for the next one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions