-
Notifications
You must be signed in to change notification settings - Fork 17
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
Proposal: simplified arguments passing and first-class support of in parameters #16
Comments
Hello! About the I won't change By the way, I'm not getting any warnings on your About method calls: I actually wanted to have something like your proposed I replaced that with I added a couple things to reduce the verbosity: if your method has no overloads, you don't need to specify any parameter type, and the single method will get picked up. Besides, I don't think the following is that bad: Ldc_I4(10);
Ldc_I4(2);
Call(new MethodRef(typeof(Math), nameof(Math.Max), typeof(int), typeof(int)));
return Return<int>(); Can you point me to some code where this is a burden? Maybe I could figure something out. |
It depends on the FxCop rule set. I'm using custom rule set which warns about unused parameters
I'm ok with this.
Yes, it's unreliable. Anyway, when you using pure IL code you're on dangerous path by default. IMO,
This way produces unnecessary local variable. Of course, the original proposal can be modified. For instance, |
One more thing: what about to add generic versions of ldobj, stobj, sizeof etc? It will be less verbose than using |
Oh, ok so that's FxCop. I suppose
That's exactly the implementation of
You don't lose it, InlineIL checks for the method presence and fails the build if it's not there.
Yes, that would be a very unsafe method indeed. It would only work if you call a method where all the parameters are Honestly I wouldn't be comfortable using it, ever. I'm not even comfortable providing something like that in the API. Unless maybe if I add a check for correct usage (which would make sure you use it for all of the parameters of a method call).
I could add that. I'm not sure I'll like the syntax though, it may not feel very consistent with the existing methods. |
For |
Also, I see obvious usefulness for |
Ok, here are some thoughts about your
(As an aside: I kind of regret introducing constructors for
Sizeof(typeof(T));
Pop(out uint size);
Push(size);
Sizeof(typeof(G));
Ceq();
Dup();
Brfalse(methodExit);
Pop(); This could have been replaced with C# code like: if (Unsafe.SizeOf<T>() == Unsafe.SizeOf<G>()) The JIT will always optimize a call to
return Intrinsics.Compare(
ref Unsafe.AsInRef<T, byte>(first),
ref Unsafe.AsInRef<G, byte>(second),
Unsafe.SizeOf<T>()
); You'd need to write an equivalent to [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TTo AsInRef<TFrom, TTo>(in TFrom source)
{
Ldarg(nameof(source));
return ref IL.ReturnRef<TTo>();
} I'd argue this would be more readable than: Ldarg(nameof(first));
Ldarg(nameof(second));
Sizeof(typeof(T));
Conv_I8();
Push(Intrinsics.Compare(ref ArgRef<byte>(), ref ArgRef<byte>(), Arg<long>())
I'm not sure I understand what you mean here. |
I added that. I actually like this syntax. 👍 |
Generally, it's not always working in this way.
I meant this: Ldtoken(Console.WriteLine(Arg<string>(), Arg<object[]>()); But I forgot that many methods have void return type. |
I agree that my code can be rewritten with pure C#. However, I saw somewhere in the open issues for CLR that |
The issue you linked does not apply to this case: the Even if the Also, I wouldn't worry too much about |
Let's recap our discussion:
|
Yes, Overloads like As for |
You decide, you're an author of this add-in 😉 Interesting fact: I tested |
Yes, that's interesting... I wrote a quick SharpLab test which seems to show that Roslyn always goes through a local variable when using the But that means your rewritten code still should use a local variable for the switch... Could you send me that benchmark please, if you still have it? I'd be interested in playing a bit with it. |
Yup, benchmark result for the current implementation: https://sakno.github.io/dotNext/benchmarks.html Another proof that constant propagation will not lead to inlining of |
Thanks, I'll take a look at this (maybe tomorrow). Just a quick note about Compare this demo in .NET Core and .NET Framework targets and see the difference. |
I took a quick look and I actually got better results with my implementation:
Here's my public static bool EqualsAlt<G>(in T first, in G second)
where G : struct
{
if (UnsafeTools.SizeOf<T>() != UnsafeTools.SizeOf<G>())
return false;
switch (UnsafeTools.SizeOf<T>())
{
case 0:
return true;
case 1:
return UnsafeTools.AsInRef<T, byte>(first) == UnsafeTools.AsInRef<G, byte>(second);
case 2:
return UnsafeTools.AsInRef<T, short>(first) == UnsafeTools.AsInRef<G, short>(second);
case 4:
return UnsafeTools.AsInRef<T, int>(first) == UnsafeTools.AsInRef<G, int>(second);
case 8:
return UnsafeTools.AsInRef<T, long>(first) == UnsafeTools.AsInRef<G, long>(second);
default:
return Intrinsics.EqualsAligned(
ref UnsafeTools.AsInRef<T, byte>(first),
ref UnsafeTools.AsInRef<G, byte>(second),
UnsafeTools.SizeOf<T>()
);
}
} Here's internal static class UnsafeTools
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int SizeOf<T>()
{
Sizeof(typeof(T));
return Return<int>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TTo AsInRef<TFrom, TTo>(in TFrom source)
{
Ldarg(nameof(source));
return ref ReturnRef<TTo>();
}
} I've changed the benchmarks so they return the values, and also updated to BenchmarkDotNet 0.12.1 in order to get a better disassembly with [Benchmark]
public bool GuidEqualsMethod()
=> NonEmptyGuid.Equals(default);
[Benchmark]
public bool GuidBitwiseEqualsMethod()
=> BitwiseComparer<Guid>.Equals<Guid>(NonEmptyGuid, default);
[Benchmark]
public bool GuidBitwiseEqualsAltMethod()
=> BitwiseComparer<Guid>.EqualsAlt<Guid>(NonEmptyGuid, default); The difference comes from the fact that the JIT is able to emit a tail call to ; DotNext.BitwiseComparer`1[[System.Guid, System.Private.CoreLib]].EqualsAlt[[System.Guid, System.Private.CoreLib]](System.Guid ByRef, System.Guid ByRef)
mov r8d,10
mov rax,offset DotNext.Runtime.Intrinsics.EqualsAligned(Byte ByRef, Byte ByRef, Int64)
jmp rax
movzx eax,al
ret
; Total bytes of code 23 Whereas it emits a normal call in yours: ; DotNext.BitwiseComparer`1[[System.Guid, System.Private.CoreLib]].Equals[[System.Guid, System.Private.CoreLib]](System.Guid ByRef, System.Guid ByRef)
sub rsp,28
mov r8d,10
call DotNext.Runtime.Intrinsics.EqualsAligned(Byte ByRef, Byte ByRef, Int64)
movzx eax,al
add rsp,28
ret
; Total bytes of code 23 I'm pretty sure you'd get the same results if you tweaked the IL a bit, but if the more readable version has the same perf, there's no real reason not to take it. 😉 |
Many thanks for such investigation!! I owe you a beer 🍺 I didn't use multiple returns due to absence of this optimization. Now I see that it's not a problem because just one branch survive after optimization. |
You're welcome! 😄 I didn't do much BTW, I just stopped after the first iteration. 😉 Actually, I thought about it a bit more and noticed it was strange that there was no inlining happening. Then, I noticed that your benchmark code actually runs in Debug mode! 😱 See, you added You can add I'm actually getting worse results with that change, but I didn't look into it further just yet. |
Hmm, Disassembly diagnoser is a luxury for me because I'm sitting on Linux and it is not supported for .NET Core, only for Mono. |
Yes, I didn't notice it at first because I expected BDN to complain, but I guess it doesn't since you're basically doing a custom build. It writes The disassembly diagnoser has been massively improved and is now cross-platform in BDN 0.12.1. 😉 |
Also I found that switch expression produces worse result rather than switch statement. In IL I found that Roslyn generates redundant temporary variables. |
dotnet crashed with SIGABRT Unfortunately disassembler is not working, at least as easy as on Windows 😢 |
I guess you should report an issue then, as it was supposed to work. Maybe some configuration in your benchmarks is preventing it from working. |
Yes, sure. I need to inspect crash dump before this. It takes some time. Also, you was right about configuration. I fixed everything and found the explanation why optimized build demonstrates worse result. It was because of my unawareness about how to correctly write benchmarks. If benchmark checks something that has return value then benchmark method should have return type as well. |
@ltrzesniewski , thanks a lot! |
Hi Lucas!
I'm using InlineIL widely in my project .NEXT. Your add-in helping me a lot to write performance-critical code. However, I feel some inconvenience with it.
The first one is an absence of support in-modifiers. Let's take a look at this code:
I have to use
Ldarg(nameof(input))
andLdarg(nameof(output))
because there is no overloads with out and in parameters. This cause unwanted warnings from Roslyn about unused parameters.The second issue is a method call. It's verbose. I like
Push
helper that allows to push result of parameterless method or property on the evaluation stack. However, this approach is not working for call sites when arguments are not locals or parameters. In this case I have to useCall
with call site descriptor object. I would like to proposeT Arg<T>()
andref T RefArg<T>()
helper which allow to load argument from the evaluation stack:Additionally, it would be great if InlineIL will be able to recognize params correctly:
The text was updated successfully, but these errors were encountered: