为什么开放类型谓词<;T>;比它的对手慢
本文关键字:对手 gt 谓词 类型 lt 为什么 | 更新日期: 2023-09-27 18:26:10
为什么Id=999999比使用谓词的lambda表达式驱动的比较快?
也许我的测试本身并不是100%相同,但这只是一个展示一般问题的示例:谓词是否比公共Id=999999慢?
谓词需要150毫秒,而普通比较需要125毫秒。从哪里来
差异/开销?你可能会问我为什么喜欢25毫秒。嗯。。。我在层次查找方法中也使用了开放类型谓词,差距要大得多。
所以我想lambda(为每个"u"创建一个委托)+谓词是问题所在?如果不是,我的设置有什么问题?
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);
}
我认为速度上有一个小差异,但我的第一点是,在您的测试中,我需要改进一些地方。在构建这些微基准测试时,始终遵守以下几条规则是很重要的:
- 计时时,请使用秒表而不是DateTime,因为它的分辨率更高,也更精确
- 在运行之前,请始终确保对所有测试中的代码进行一次预热。否则,代码的第一位将有运行速度较慢的趋势,因为它承担了JIT'ing的大部分成本
- 始终多次运行测试(您在示例中已经这样做了)
如果我修改你的测试,我会得到这样的结果:
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
不过,总的来说,我希望在通过本机调用调用delegate/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%的时间进行这种调用(例如),那么在执行代码时,这相当于一个绝对微小的差异。
我希望委托的速度会慢一些。在所有的委托参数都应该加载到寄存器或其他地方,然后应该执行一个方法调用,这是一个又一个跳转指令。你为什么期望他们是一样的?