ChilliCream / graphql-platform

Welcome to the home of the Hot Chocolate GraphQL server for .NET, the Strawberry Shake GraphQL client for .NET and Banana Cake Pop the awesome Monaco based GraphQL IDE.

Home Page:https://chillicream.com

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Optimize FilterExpressionBuilder by using cached delegates and MethodInfo

nikolai-mb opened this issue · comments

Product

Hot Chocolate

Is your feature request related to a problem?

Hey guys!

I submitted a PR to fix the expression constant issue which was merged a while back: #6711

While looking through the code for that PR i was wondering if it would be possible to optimize the expression logic for the filter expression builder even further.

There's a couple of low hanging fruits as far as i can tell

The solution you'd like

, related to:

Contains expression

The Expression.Call overload used in the FilterExpressionBuilder.In method causes about 5 KB of allocations from the method call alone, since the underlying expression code retrieves all methods from the Enumerable type on each invocation. Using a cached MethodInfo here results in approximately 95% improvement in both speed and allocations, see benchmarks below.

Cached delegates instead of reflection

Currently, all the filter expressions uses reflection to invoke the generic CreateAndConvertParameter method. Instead of using reflection every time a filter expression is built, it's possible to cache the delegate after the call to MakeGenericMethod and then use the delegate directly instead through reflection.

This eliminates a reflection call, multiple cases of boxing and reflection argument allocations while also being around twice as fast.

I've tested the changes in one of our API's and the results seem to be quite substantial, especially for the IN / NIN filters. I've tested against a moderate set of different queries with combinations of different operators and everything seems to be running great.

Benchmarks

hc-delegate-optimization

The code itself is not altered in any significant way, and this was achieved by only changing the internal code of the FilterExpressionBuilder and introducing a couple of ConcurrentDictionary instances to store the cached delegates and method-info's for the different types.

I'll throw together a PR some time this week, any initial thoughts on this @PascalSenn or @michaelstaib ? This seems like a great improvement considering HC's performance goals, but for all i know there might be reasons that this hasn't been done already, let me know if you see anything obviously wrong with this approach, otherwise i'll get some actual code for you to look at soon :)

Yes go ahead ... we have a couple of things we are doing for the Data APIs in Hot Chocolate for V14 ... so just go ahead with a PR ... I will see that it gets through quickly. If you are on slack (slack.chillicream.com) you can also ping me quickly for a huddle if you get stuck.

@michaelstaib I think i've done what i can for now. Give the PR a look when you have the chance. Ended up optimising more cases than i anticipated, but i think the gains were quite nice given the small amount of code needed and this being a potential hot path for queries with many filters. Let me know what you think or if there's something i've overlooked

@PascalSenn can you have a look at this one?