8

(this is via a question on twitter, re-asked here with permission)

I'm trying to validate some objects quickly (to test for nulls), and I thought FastMember might be able to help - however, with the tests shown below I am seeing much worse performance. Am I doing something wrong?

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;
        }
    }
4

1 回答 1

6

这里的主要问题是您将元编程的一次性成本包含在时序中。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);
}

这显示 fast-member 快了一点,但没有我想要的那么多 - 600ms(反射)vs 200ms(FastMember);很可能 1.0.11 的变化过于偏向大类(使用 1.0.10 只需要 130 毫秒)。我可能会发布一个 1.0.12,它对小类和大类使用不同的策略来进行补偿。

然而!就您而言,如果您只想测试null,我实际上会认真考虑直接通过 IL 优化该案例。

例如,相同的测试只需要 45 毫秒:

[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);
}

使用:

public static bool IsValidMetaprogramming<T>(T actions)
{
    return !NullTester<T>.HasNulls(actions);
}

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>));
    }
}
于 2013-10-01T19:35:17.360 回答