C#
C#

help

Root Question Message

LPeter1997
LPeter199710/30/2022
Speed up reflection-based comparison

We have a type that we have no access to. Not now, never. And we can't replace it. We have a couple of private fields in it that we need to compare inside two instances to determine if they are equivalent for our purposes. Now the thing is, the place where the comparisons happen is super performance-critical. Is there a way to build up and "compile" a comparison function like this at runtime purely from reflection info to speed this up?
LPeter1997
LPeter199710/30/2022
It could be that what I need is expression trees?
LPeter1997
LPeter199710/30/2022
For reasons it would be nice not to use 3rd party here
LPeter1997
LPeter199710/30/2022
@179984375075700736 didn't you send replies here
canton7
canton710/30/2022
Are you caching the FieldInfo etc? That's the first big win
canton7
canton710/30/2022
Compiled expressions will give you a little performance boost on top of that
LPeter1997
LPeter199710/30/2022
I guess the best course of action is to set up a test with BDN and see for myself
LPeter1997
LPeter199710/30/2022
Diddy acting... weird again
LPeter1997
LPeter199710/30/2022
No bueno
LPeter1997
LPeter199710/30/2022
Impossible
LPeter1997
LPeter199710/30/2022
Changes all the time and there are many-many different types
LPeter1997
LPeter199710/30/2022
(They are the async state machines the functions generate)
LPeter1997
LPeter199710/30/2022
Anyone interested, this is what I ended up with, performs ok: https://gist.github.com/LPeter1997/47886848e145fb61eb49c32344c03abf
(Note: the code since has been updated to accommodate release mode, but I don't think anyone else would need this ever :when: )
canton7
canton710/30/2022
Nit: if you do Expression.Lambda<Func<...>>(...), you don't then need to cast the result of lambda.Compile()
LPeter1997
LPeter199710/30/2022
Oh I didn't know that, thanks
canton7
canton710/30/2022
What's the need for the Unsafe.As?
canton7
canton710/30/2022
Also, you call Unsafe.As once for each field, rather than once
LPeter1997
LPeter199710/30/2022
I don't want to cast the member each time to the concrete state machine type, because I know it's that type anyway, and this is running in critical code
canton7
canton710/30/2022
Ah, I see. I'd use Expression.Convert there
LPeter1997
LPeter199710/30/2022
So no need for the typecheck each time
canton7
canton710/30/2022
But even so, you can store the result of Unsafe.As in a variable
LPeter1997
LPeter199710/30/2022
I know but I also don't know how to factor it out to only have to cast it once. I haven't used the expr tree in years and IIRC they were sort of for single expressions. Like I don't recall being able to declare locals
LPeter1997
LPeter199710/30/2022
My knowledge is (likely) seriously outdated on this tho
LPeter1997
LPeter199710/30/2022
Sadly in practice, my code will likely write a convert anyway, because in release mode, Roslyn generates struct state machines, which means Unsafe.As is illegal anyway :pepehands:
canton7
canton710/30/2022
Expression.Block.

var castParam1 = Expression.Variable(...);
...
var blockMembers = new Expression[]
{
    Expression.Assign(castParam1, unsafeAs),
    Expression.Assign(castParam2, unsafeAs),
    comparisonsConjuncted,
};
var block = Expression.Block(new[] { castParam1, castParam2 }, blockMembers);
var lambda = Expression.Lambda<...>(block, new[] { param1, param2 });
canton7
canton710/30/2022
(and use castParam1 etc in comparisons of course)
canton7
canton710/30/2022
The last expression in a block is the return value. You need to give it the list of variables used in the block as well
LPeter1997
LPeter199710/30/2022
Does it implement them (locals) with closures or something
canton7
canton710/30/2022
No. It translates to a BasicBlock of instructions
canton7
canton710/30/2022
Just a set of things executed one after another
canton7
canton710/30/2022
System.Linq.Expressions aren't always pure expressions 😛
canton7
canton710/30/2022
(IIRC this stuff was added on in C# 4 for dynamic support)
LPeter1997
LPeter199710/30/2022
I'll hammer my hash-code function into this form to test it first, that's simpler
LPeter1997
LPeter199710/30/2022
Yeah this doesn't work, I'm getting the exception I kind of expected
LPeter1997
LPeter199710/30/2022
I might be screwing something up tho
    private static AsmGetHashCodeDelegate CreateHashCodeFunc(Type asmType)
    {
        var getTypeMethod = asmType.GetMethod(nameof(GetType))!;
        var toHashCodeMethod = typeof(HashCode).GetMethod(nameof(HashCode.ToHashCode))!;

        var param = Expression.Parameter(typeof(IAsyncStateMachine));

        var hashCode = Expression.Variable(typeof(HashCode));
        var asmAsConcreteType = Expression.Variable(asmType);

        var blockExprs = new List<Expression>();
        blockExprs.Add(Expression.Assign(hashCode, Expression.Default(typeof(HashCode))));
        blockExprs.Add(Expression.Assign(asmAsConcreteType, CastToConcreteType(param, asmType)));

        foreach (var field in GetRelevantFields(asmType))
        {
            blockExprs.Add(Expression.Call(
                instance: hashCode,
                methodName: nameof(HashCode.Add),
                typeArguments: new[] { field.FieldType },
                arguments: Expression.MakeMemberAccess(asmAsConcreteType, field)));
        }

        blockExprs.Add(Expression.Call(
            instance: hashCode,
            method: toHashCodeMethod));

        var block = Expression.Block(blockExprs);
        var lambda = Expression.Lambda<Func<IAsyncStateMachine, int>>(block, param);

        return new(lambda.Compile());
    }
LPeter1997
LPeter199710/30/2022
System.InvalidOperationException: 'variable '' of type 'System.HashCode' referenced from scope '', but it is not defined'
canton7
canton710/30/2022
You need to give it the list of variables used in the block as well
LPeter1997
LPeter199710/30/2022
Oh crap yes the block
canton7
canton710/30/2022
So Expression.Block(new[] { hashCode, asmAsConcreteType }, ...)
LPeter1997
LPeter199710/30/2022
Yea yea
LPeter1997
LPeter199710/30/2022
Ok, this works, thanks
LPeter1997
LPeter199710/30/2022
Weird design, since we are working with symbols effectively already
canton7
canton710/30/2022
It makes compilation easier I think?
ContactFrequently Asked QuestionsJoin The DiscordBugs & Feature RequestsTerms & Privacy