FastExpressionCompiler icon indicating copy to clipboard operation
FastExpressionCompiler copied to clipboard

Check usage of C#9 Function Pointers

Open jogibear9988 opened this issue 5 years ago • 5 comments

I don't know, but could C#9 function pointers help to get more perfomance?

https://docs.microsoft.com/de-de/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers

jogibear9988 avatar Nov 18 '20 21:11 jogibear9988

In theory yes. It use another IL opcode that calls the method instead of a virtual call to the Invoke() method behind. However in practice I wonder if the JIT can eliminate these virtual call to Invoke() and call the method directly.

ebfortin avatar Dec 27 '20 21:12 ebfortin

My main quuestion what should I call with the pointers or calli op-code. My only idea is to use it for inviking the nested lambda or the constant delegate, e.g. Invoke(Lambda(...),...). It seems a very rare case to bother about. Otherwise, for the method I am always trying to use Call if possible, which should be as fast.

dadhi avatar Dec 28 '20 04:12 dadhi

You could replace all delegate Invoke method calls as well as lambda invokes if you wanted so long as the delegate was embedded as a constant. The JIT can devirtualize calli call sites and may even inline callis to managed functions in the future.

If the Delegate has a non-null Target (the MethodInfo member has IsStatic == false) the signature for calli is fn(<Target>, <arguments ...>) otherwise it's fn(<arguments ...>) ... the value to give to calli must be the address from MethodInfo.MethodHandle.GetFunctionPointer() but MethodHandle and/or GetFunctionPointer can throw due to platform implementation / limitations / various reasons, and you'd need to fall back to calling the Invoke member of Delegate.

It is an decent optimization, though you'd have to bench it to see the performance difference.

If you can eliminate the embedding and invocation of a delegate it is generally a big win compared to e.g. arithmetic and tail call optimizations. calli can benefit from tail call optimizations in some cases too.

TYoungSL avatar Jan 16 '23 19:01 TYoungSL

@TYoungSL Great. Maybe you can provide a small example of the IL for the Expression.Invoke for the delegate constant expression?

If you can eliminate the embedding and invocation of a delegate it is generally a big win

Yes, but it is hard to do generally. So you need to have a fitting scenario for this. And I did not yet work with such. Maybe you have an example in mind?

dadhi avatar Jan 16 '23 19:01 dadhi

I'll give it a shot.

The use case I have we workaround by generating a dynamic static method with AggressiveInlining that does the calli; the JIT does a good job with that. The calli are to unmanaged code though.

The function pointer is constant but the arguments change, we have to create stubs for any new static delegates / function pointers we want a fast calli to.

We expand some delegate calls using Expression.Call on the MethodInfo and Target of the delegate before the FEC/Linq.Expressions even gets to see them. It wouldn't make sense to use calli there.

It might make more sense to translate some delegate Invoke call to a direct call or callvirt like that.

For managed calli to make sense the Delegate would have to be the type of an external ParameterExpression (not a local variable).

TYoungSL avatar Jan 16 '23 19:01 TYoungSL