Mor*_*eng 14 c# performance virtual delegates
我刚刚遇到一个代码设计问题.说,我有一个"模板"方法,可以调用一些可能"改变"的函数.直观的设计是遵循"模板设计模式".将更改函数定义为要在子类中重写的"虚拟"函数.或者,我可以使用没有"虚拟"的委托功能.注入委托函数,以便它们也可以自定义.
最初,我认为第二种"委托"方式比"虚拟"方式更快,但是一些编码片段证明它不正确.
在下面的代码中,第一个DoSomething方法遵循"模板模式".它调用虚方法IsTokenChar.第二个DoSomthing方法不依赖于虚函数.相反,它有一个传入代理.在我的电脑中,第一个DoSomthing总是比第二个快.结果如1645:1780.
"虚拟调用"是动态绑定,应该比直接委托调用更耗时,对吗?但结果表明事实并非如此.
有人可以解释一下吗?
using System;
using System.Diagnostics;
class Foo
{
public virtual bool IsTokenChar(string word)
{
return String.IsNullOrEmpty(word);
}
// this is a template method
public int DoSomething(string word)
{
int trueCount = 0;
for (int i = 0; i < repeat; ++i)
{
if (IsTokenChar(word))
{
++trueCount;
}
}
return trueCount;
}
public int DoSomething(Predicate<string> predicator, string word)
{
int trueCount = 0;
for (int i = 0; i < repeat; ++i)
{
if (predicator(word))
{
++trueCount;
}
}
return trueCount;
}
private int repeat = 200000000;
}
class Program
{
static void Main(string[] args)
{
Foo f = new Foo();
{
Stopwatch sw = Stopwatch.StartNew();
f.DoSomething(null);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
{
Stopwatch sw = Stopwatch.StartNew();
f.DoSomething(str => String.IsNullOrEmpty(str), null);
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
}
}
Run Code Online (Sandbox Code Playgroud)
Jon*_*eet 20
想想每种情况下的要求:
虚拟电话
代表电话
可能会有一些优化,因此单呼叫情况不会涉及循环,但即使这样也需要非常快速的检查.
但基本上,代表所涉及的间接性也是如此.鉴于我在虚拟方法调用中不确定的位置,在大规模深层次类型层次结构中调用未重载的虚拟方法可能会更慢......我将试一试并使用答案进行编辑.
编辑:我已尝试使用继承层次结构深度(最多20个级别),"最多派生重写"和声明的变量类型 - 并且它们中没有一个似乎有所作为.
编辑:我刚刚尝试使用接口(传入)的原始程序 - 最终具有与委托相同的性能.
小智 12
只是想为约翰双向飞碟的回应添加一些修正:
虚方法调用不需要执行空检查(使用硬件陷阱自动处理).
它也不需要走向继承链来查找非重写方法(这就是虚方法表的用途).
在调用时,虚方法调用本质上是一个额外的间接级别.由于表查找和后续函数指针调用,它比普通调用慢.
委托调用还涉及额外的间接级别.
除非您使用DynamicInvoke方法执行动态调用,否则对委托的调用不涉及将参数放入数组中.
委托调用涉及调用方法,在有问题的委托类型上调用编译器生成的Invoke方法.对谓词(值)的调用变为predicator.Invoke(value).
反过来,Invoke方法由JIT实现,以调用函数指针(存储在委托对象内部).
在您的示例中,您传递的委托应该已经实现为编译器生成的静态方法,因为实现不访问任何实例变量或本地,因此从堆中访问"this"指针的需要应该不是问题.
委托和虚函数调用之间的性能差异应该大致相同,并且您的性能测试表明它们非常接近.
差异可能是由于多播需要额外的检查+分支(如John所建议的).另一个原因可能是JIT编译器没有内联Delegate.Invoke方法,并且Delegate.Invoke的实现不执行参数以及执行虚方法调用时的实现.
虚拟调用在内存中的已知偏移量处取消引用两个指针.它实际上不是动态绑定; 在运行时没有代码反映元数据以发现正确的方法.编译器根据this指针生成几条指令来执行调用.实际上,虚拟调用是单个IL指令.
谓词调用正在创建一个匿名类来封装谓词.必须实例化该类,并且生成一些代码以实际检查谓词函数指针是否为空.
我建议你看看两者的IL结构.只需调用两个DoSomthing中的每一个,即可编译上面源代码的简化版本.然后使用ILDASM查看每个模式的实际代码.
(我相信我会因为没有使用正确的术语而被投票:-))
由于您没有任何覆盖虚拟方法的方法,因此 JIT 可能能够识别这一点并使用直接调用。
对于这样的事情,通常最好像您所做的那样对其进行测试,而不是尝试猜测性能如何。如果您想了解有关委托调用如何工作的更多信息,我建议您阅读 Jeffrey Richter 撰写的优秀书籍“CLR Via C#”。
| 归档时间: |
|
| 查看次数: |
12520 次 |
| 最近记录: |