riok / mapperly

A .NET source generator for generating object mappings. No runtime reflection.
https://mapperly.riok.app
Apache License 2.0
2.5k stars 136 forks source link

Using AggressiveInlining for generated mapping methods #1381

Open mjebrahimi opened 1 month ago

mjebrahimi commented 1 month ago

According to this benchmark, it shows using [MethodImpl(MethodImplOptions.AggressiveInlining)] for mapping structs is much faster than not using it.

I must confess that much performance difference was surprising to me but in fact, it is! and why don't we advantage of it? :)

The generated mapping methods are simple and seem safe to use with AggressiveInlining, so I think there's nothing wrong with it (at least it's worth a try).

Note: mapping methods in this benchmark are mapperly-generated and I just edited them to decorate with AggressiveInlining.

Benchmark

mjebrahimi commented 1 month ago

By the way, I updated my .NET Mappers Benchmark and wanted to share it with you @latonz

Benchmark

latonz commented 1 month ago

Thanks for the benchmark and the PR contribution. The performance improvements on the struct methods are impressive 😮👍 . I'm still not sure if applying aggressive inlining to all mapping methods is the best option. We should probably discuss this in more detail... My initial thoughts:

What do you think?

mjebrahimi commented 1 month ago

I agree it needs to be discussed more. Unfortunately, there is not enough information and documents for it on microsoft docs. Some developers say something about it but most of them are not based on references and cannot be cited.

After a lot of research, the disadvantages that I'm aware and sure of, are:

One thing that I read somewhere (not in docs/reference) but I'm not sure about:

There is another thing that is worth mentioning:

The only main advise that I have seen many places:

The .NET JIT compiler is designed to make intelligent inlining decisions. Trust it to handle the majority of inlining decisions, only use AggressiveInlining when you have clear evidence it will benefit performance.

About your questions:

but in a large object hierarchy a method for an object mapping nested deep in the object hierarchy would be duplicated a lot.

Yes, duplicate calls can leads to increased the size of the assembly, but on the otherside in a large object hierarchy with many nested mapping, using AggressiveInlining has more peformance improvement impact (as its main advantage is reducing method calls which applies more to every nested mapping methods)

Why doesn't the compiler inline these methods without the attribute?

The compiler is smart to identify most of the qualified methods for inlining but not smart enough to identify all (otherwise, it should not exist at all). And that is why developers used aggressive inlining hints in many places in .net/aspnetcore/efcore /roslyn teams.

Does the benchmark lead to the same results if the mapper is static?

No there is not difference between static and instance mapper classes. Note: I expanded the level of nested mapping methods to see better the impact of using AggressiveInlining.

Benchmark-instance

As the attributes on partial methods are merged, could an alternative be to document this in detail and let the user apply the attribute to the partial definition themselves?

As you mentioned, it does not apply to nested methods, so we lose the main avantage of it (eliminating the cost of method calls)

Benchmark-static

As a conclusion I thinks it's worth to experiment this feature in real large projects within community and get feedback from them. So we can release this feature as a preview version of library to get feedbacks.

Thanks for bringing up various points to discuss, I'm looking forward to hearing your opinions and moving this discuss further.

latonz commented 1 month ago

Thank you for your research! My conclusion is this: I think it makes sense to implement it, but I think we need to think about the following:

The main branch targets Mapperly 4.0 (major with breaking changes), so right now would be a good time to implement this 😊

mjebrahimi commented 1 month ago

Thanks for your good points! and sorry for my late resposding :)

A configuration to enable/disable aggressive inlining

I know your concern about the feature of this featuer but exposing an Enum property is not a good choies IMO. Because other values of this enum has not much use-cases and also can lead to problems if the developer use it wrongly (such as MethodImplOptions.Synchronized)

I thinks it's better to keep it simple (KISS principle) like a bool property flag to enable/disable it. Or maybe a 3 options enum (Disabled, Enabled, and EnabledForStruct) since appreantly it's benefital almost for struct types and is not significant for class types.

If the MethodImpl attribute is present on the partial method definition, it should not be emitted by Mapperly on the generated method implementation.

Why not emitting both user specified values and our disered value? This enum is decorated with [System.Flags] so we can emit both values (user value and our value) togheter like (MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization). If the user does not want to override by us, they can easily disable it by out exposing property.

This also prevents a major breaking change that could lead to duplicate attributes.

We can add AggressiveInlining only if it's not already existed to prevent duplicaion.

The main branch targets Mapperly 4.0 (major with breaking changes), so right now would be a good time to implement this 😊

Very good! so if you agree with any of these please let me know so I can help with the implementation.

latonz commented 1 month ago

My thoughts:

Isn't this

If the user does not want to override by us, they can easily disable it by out exposing property. in contradiction to We can add AggressiveInlining only if it's not already existed to prevent duplicaion.

How would overriding work with partial methods? If the user already defines the attribute on the partial definition (e.g. as [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)], applying the attribute again (even with merged flags) on the implementation part of the method leads to a duplicated attribute, doesn't it?

Would be happy to review an implementation PR 😊

latonz commented 2 weeks ago

@mjebrahimi are you still interested in implementing this? 😊

mjebrahimi commented 1 week ago

Hi @latonz, sorry for the late response, I was on vacation. And thanks for waiting :)

I updated the code for PR https://github.com/riok/mapperly/pull/1384#issuecomment-2296602722 according to our discussion.

latonz commented 1 week ago

I hope you had a wonderful vacation and thanks for the updates 😊