为什么Id == 999999比使用谓词的lambda表达式驱动比较快?
也许我的测试本身并不是100%相同,但这只是一个展示一般问题的示例:谓词是否比普通Id = 999999慢?
谓词需要150 ms,而公共比较需要125 ms。
来自哪里差/开销?你可能会问我为什么要关心25毫秒。嗯...我在分层查找方法中也使用了开放式谓词,差距要大得多。
所以我猜lambda(为每个“你”代表创建)+谓词是问题吗?如果没有我的设置有什么问题?
public class UtilitiesTest
{
[Test]
public void Go()
{
var units = GetUnits();
DateTime d = DateTime.Now;
var item = units.testme<MyUnit>(u => u.Id == 999999);
TimeSpan t = DateTime.Now - d;
Debug.WriteLine(t.TotalMilliseconds + " ms");
var units1 = GetUnits();
DateTime d1 = DateTime.Now;
MyUnit item1 = null;
foreach (MyUnit unit in units1)
{
if (unit.Id == 999999)
{
item1 = unit;
break;
}
}
TimeSpan t1 = DateTime.Now - d1;
Debug.WriteLine(t1.TotalMilliseconds + " ms");
}
private IEnumerable<MyUnit> GetUnits()
{
for (int i = 0; i < 1000000; i++)
yield return new MyUnit() { Id = i };
}
}
class MyUnit
{
public int Id { get; set; }
}
public static T testme<T>(this IEnumerable<T> source, Predicate<T> condition) where T : class
{
foreach (T item in source)
{
if (condition(item))
{
return item;
}
}
return default(T);
}
答案 0 :(得分:3)
我认为速度差异很小,但我的第一点是在测试中我会改进一些东西。在构建这些微基准测试时,始终遵守一些规则非常重要:
如果我重做你的测试,我最终会得到这样的结果:
public void Go()
{
// warmup
Test_Equality();
Test_Lambda();
// timed tests
Console.WriteLine(Test_Equality() + " ms");
Console.WriteLine(Test_Lambda() + " ms");
}
public long Test_Lambda()
{
var units1 = GetUnits();
var stopWatch1 = new Stopwatch();
stopWatch1.Start();
MyUnit item1 = units1.testme<MyUnit>(u => u.Id == 999999);
return stopWatch1.ElapsedMilliseconds;
}
public long Test_Equality()
{
var units2 = GetUnits();
var stopWatch2 = new Stopwatch();
stopWatch2.Start();
MyUnit item2;
foreach (MyUnit unit in units2)
{
if (unit.Id == 999999)
{
item2 = unit;
break;
}
}
return stopWatch2.ElapsedMilliseconds;
}
我执行此操作,我得到的数据大致如下:
Test_Lambda: 68 ms
Test_Equality: 53 ms
总的来说,我希望在本机调用上调用委托/ lambda版本时会有很小的性能损失,就像通过委托调用方法而不是调用方法直接。在幕后,编译器正在生成额外的代码来支持这些lambda版本的测试。
最终它正在生成的行:
public class PossibleLambdaImpl
{
public bool Comparison(MyUnit myUnit)
{
return myUnit.Id == 9999999;
}
}
因此,lambda测试实际上是在每次评估时在编译器生成的类上调用一个方法。
事实上 - 当我改变你的相等测试而不是创建上面的PossibleLambdaImpl
类一次,并且每次循环调用PossibleLambdaImpl.Comparison
时,我得到的结果几乎与lambda情况相同:
public long Test_PossibleLambdaImpl()
{
var units2 = GetUnits();
var stopWatch2 = new Stopwatch();
stopWatch2.Start();
MyUnit item2;
var possibleLambdaImpl = new PossibleLambdaImpl();
foreach (MyUnit unit in units2)
{
if (possibleLambdaImpl.Comparison(unit))
{
item2 = unit;
break;
}
}
return stopWatch2.ElapsedMilliseconds;
}
[注意:本网站上还有其他人比我更了解这一点 - 但粗略地说我认为这是正确的]
无论如何,要记住的是这种性能差异很小。像这样的微观基准总是突出差异。根据您的测试,它们之间可能存在10%-20%的性能差异,但如果您的真实代码仅花费0.001%的时间进行此类调用(例如),那么这相当于执行代码。
答案 1 :(得分:0)
我希望代表会变慢。在所有委托参数应该在寄存器或其他任何内容中加载之后,然后应该执行一个方法调用,这是一个多跳转指令。你为什么期望它们是一样的?