(这是通过推特上的一个问题,经许可再次询问)
我正在尝试快速验证一些对象(以测试空值),我认为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)
这里的主要问题是您将计时中的元编程成本包括在内。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)
| 归档时间: |
|
| 查看次数: |
1542 次 |
| 最近记录: |