martinothamar / Mediator

A high performance implementation of Mediator pattern in .NET using source generators.

Repository from Github https://github.commartinothamar/MediatorRepository from Github https://github.commartinothamar/Mediator

Defensive copies for non-readonly struct queries

Tornhoof opened this issue · comments

At the moment the generator adds the in parameter modifier for read-only structs, it does not add it for non-readonly structs.
But the method signature of the wrapper always containts the in modifier.

public {{ wrapperType.ReturnTypeName }} Handle(in TRequest request, global::System.Threading.CancellationToken cancellationToken) =>
_rootHandler(request, cancellationToken);

This means, that for non-readonly structs a defensive copy is always made, which is wrong from the code-correctness perspective and for performance.
See: https://devblogs.microsoft.com/premier-developer/the-in-modifier-and-the-readonly-structs-in-c/#performance-characteristics-of-the-in-modifier for more details

Good catch! If I understand this issue correct, defensive copies are made during use (access properties etc), while by removing the in parameter a copy is also made. Can we avoid these issues by using ref instead of in on the wrapper? I think I should make some benchmarks for this to see exactly what the behavior is here. I tried to see the codegen difference for this in sharplab a while ago but was unable to produce the difference. I might be misunderstanding something here..

Yes you can use ref, but it should not be necessary.
The jit is smart enough by now, see
https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AXEBDAzgWwB8ABAJgEYBYAKGIAYACY8gOgCUBXAOwwEt8YLAMIR8AB14AbGFADKMgG68wMXAG4aNYgGYmpBkIYBvGgzMNY2ACYQukgJ4NcGKBzAYGAEWwZsx0+aBOhYw1rYODLw8DAhoDPZxAF5xAO4a1IGBANoAsjAYABYQVgCS4pIAFHmFxWVikgDyYny2uCwAchAldlFRAOYAlAC6AZlMupY2do7EKAwAQthQFQPGDAC+oxtbW8xITHMAKqoY5BXevgxWqyYZY5EAZgwVViwIDAB8DHSrr4vLA3SgU2dzMewODGOzlIFSiXh8fmu/lBmV4Txeb0+31+LH+KyB5hB6yAA===

The asm for both methods, with and without in, is the same for a read-only struct. Even if the struct is only defacto read-only, by making all fields and methods read-only it's smart enough.

That's why I said in the PR that we can let the jit decide.