为什么fastmember看起来不比这里的反射更快?

Mar*_*ell 8 .net fastmember

(这是通过推特上的一个问题,经许可再次询问)

我正在尝试快速验证一些对象(以测试空值),我认为FastMember可能会有所帮助 - 但是,通过下面显示的测试,我看到了更糟糕的性能.难道我做错了什么?

public class ValidateStuffTests
{
        [Test]
        public void Benchmark_speed()
        {
            var player = CreateValidStuffToTest();
            _stopwatch.Start();
            CharacterActions.IsValid(player);
            _stopwatch.Stop();
            Console.WriteLine(_stopwatch.Elapsed);
            Assert.Less(_stopwatch.ElapsedMilliseconds, 10, string.Format("IsValid took {0} mileseconds", _stopwatch.Elapsed));

        }

        [Test]
        public void When_Benchmark_fastMember()
        {
            var player = CreateValidStuffToTest();
            _stopwatch.Start();
            CharacterActions.IsValidFastMember(player);
            _stopwatch.Stop();
            Assert.Less(_stopwatch.ElapsedMilliseconds, 10, string.Format("IsValid took {0} mileseconds", _stopwatch.Elapsed));

        }
}

public static class ValidateStuff
    {
        public static bool IsValid<T>(T actions)
        {
            var propertyInfos = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (var property in propertyInfos)
            {
                if (property.GetValue(actions, null) == null)                               
                    return false;               
            }
            return true;
        }

        public static bool IsValidFastMember<T>(T actions)
        {
            var typeAccessor = TypeAccessor.Create(typeof(T));

            foreach (var property in typeAccessor.GetMembers())
            {
                if (typeAccessor[actions, property.Name] == null)               
                    return false;               
            }
            return true;
        }
    }
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 5

这里的主要问题是您将计时中的元编程成本包括在内。FastMember在处理类型并生成合适的IL时会产生一些开销,当然:所有IL生成层然后都需要JIT。因此,可以使用一次:FastMember可能看起来更昂贵。确实,如果只要做一次这项工作,您就不会使用 FastMember之类的东西(反射就可以了)。诀窍是在计时之外(在两个测试中)一次进行所有操作,以使首次运行性能不会偏向结果。而且,在性能方面,您通常需要运行多次。这是我的装备:

const int CYCLES = 500000;
[Test]
public void Benchmark_speed()
{
    var player = CreateValidStuffToTest();
    ValidateStuff.IsValid(player); // warm up
    var _stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < CYCLES; i++)
    {
        ValidateStuff.IsValid(player);
    }
    _stopwatch.Stop();
    Console.WriteLine(_stopwatch.Elapsed);
    Console.WriteLine("Reflection: {0}ms", _stopwatch.ElapsedMilliseconds);
}

[Test]
public void When_Benchmark_fastMember()
{
    var player = CreateValidStuffToTest();
    ValidateStuff.IsValidFastMember(player); // warm up
    var _stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < CYCLES; i++)
    {
        ValidateStuff.IsValidFastMember(player);
    }
    _stopwatch.Stop();
    Console.WriteLine("FastMember: {0}ms", _stopwatch.ElapsedMilliseconds);
}
Run Code Online (Sandbox Code Playgroud)

这表明快速成员的速度稍快一些,但没有我想要的那么快-600ms(反射)vs 200ms(FastMember);1.0.11的更改很可能将事情偏向大型类(使用1.0.10只需130毫秒)。我可能会发布一个1.0.12版本,该版本对小型和大型类使用不同的策略进行补偿。

然而!在您的情况下,如果您要测试的只是null,我实际上会认真考虑直接通过IL优化该情况。

例如,以下时间仅需45ms即可完成同一测试:

[Test]
public void When_Benchmark_Metaprogramming()
{
    var player = CreateValidStuffToTest();
    Console.WriteLine(ValidateStuff.IsValidMetaprogramming(player)); // warm up
    var _stopwatch = Stopwatch.StartNew();
    for (int i = 0; i < CYCLES; i++)
    {
        ValidateStuff.IsValidMetaprogramming(player);
    }
    _stopwatch.Stop();
    Console.WriteLine("Metaprogramming: {0}ms", _stopwatch.ElapsedMilliseconds);
}
Run Code Online (Sandbox Code Playgroud)

使用:

public static bool IsValidMetaprogramming<T>(T actions)
{
    return !NullTester<T>.HasNulls(actions);
}
Run Code Online (Sandbox Code Playgroud)

还有一些疯狂的元编程代码,可以T在一处对所有给定的代码进行测试:

static class NullTester<T>
{
    public static readonly Func<T, bool> HasNulls;

    static NullTester()
    {
        if (typeof(T).IsValueType)
            throw new InvalidOperationException("Exercise for reader: value-type T");

        var props = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
        var dm = new DynamicMethod("HasNulls", typeof(bool), new[] { typeof(T) });
        var il = dm.GetILGenerator();

        Label next, foundNull;
        foundNull = il.DefineLabel();
        Dictionary<Type, LocalBuilder> locals = new Dictionary<Type, LocalBuilder>();
        foreach (var prop in props)
        {
            if (!prop.CanRead) continue;
            var getter = prop.GetGetMethod(false);
            if (getter == null) continue;
            if (prop.PropertyType.IsValueType
                && Nullable.GetUnderlyingType(prop.PropertyType) == null)
            {   // non-nullable value-type; can never be null
                continue;
            }
            next = il.DefineLabel();
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Callvirt, getter);
            if (prop.PropertyType.IsValueType)
            {
                // have a nullable-value-type on the stack; need
                // to call HasValue, which means we need it as a local
                LocalBuilder local;
                if (!locals.TryGetValue(prop.PropertyType, out local))
                {
                    local = il.DeclareLocal(prop.PropertyType);
                    locals.Add(prop.PropertyType, local);
                }
                il.Emit(OpCodes.Stloc, local);
                il.Emit(OpCodes.Ldloca, local);
                il.Emit(OpCodes.Call,
                    prop.PropertyType.GetProperty("HasValue").GetGetMethod(false));
                il.Emit(OpCodes.Brtrue_S, next);                 
            }
            else
            {
                // is a class; fine if non-zero
                il.Emit(OpCodes.Brtrue_S, next);
            }
            il.Emit(OpCodes.Br, foundNull);
            il.MarkLabel(next);
        }
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Ret);
        il.MarkLabel(foundNull);
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Ret);

        HasNulls = (Func<T, bool>)dm.CreateDelegate(typeof(Func<T, bool>));
    }
}
Run Code Online (Sandbox Code Playgroud)