昨天在研究C#的定时器的过程中,发现 Delegate的效率比直接调用一个函数的效率要低不少,今天研究了一下,的确如此。在网上查了一下
http://www.cnblogs.com/sumtec/archive/2004/05/23/11025.aspx
也写了一个程序进行测试,但给出的程序不是太合理,因为在调用接口注册的函数时使用了for循环,操作明显多于delegate测试函数,所以执行结果反而是delegate比接口调用要快很多,于是我又写了如下程序进行测试:
interface IVoidMethod
{
void WorkMethod();
}
interface IDelegateTester
{
void Run();
}
abstract class DelegateTester : IDelegateTester, IVoidMethod
{
private int _multiCast;
private long _iteration;
protected delegate void TaskHandler();
protected event TaskHandler task;
public int MultiCast
{
get { return _multiCast; }
set { _multiCast = value; }
}
public long Iteration
{
get { return _iteration; }
set { _iteration = value; }
}
protected DelegateTester(int multicast, long iteration)
{
_multiCast = multicast;
_iteration = iteration;
while (multicast-- > 0) {
task += new TaskHandler(WorkMethod);
}
}
protected void CallDelegate()
{
//if (task != null)
{
task();
}
}
protected abstract void CallFunction();
/// <summary>
/// work method for test
/// </summary>
public virtual void WorkMethod()
{
}
#region IDelegateTester Members
public void Run()
{
Console.WriteLine("/n Delgate Test(Multicast={0}, iteration={1}", MultiCast, Iteration);
DateTime t0 = DateTime.Now;
Console.WriteLine(t0.ToString("mm:ss.ffff/t:") + "Runing delegate test ...");
for (int i = 0; i < _iteration; i++) {
CallDelegate();
}
DateTime t1 = DateTime.Now;
Console.WriteLine(t1.ToString("mm:ss.ffff/t:") + "Runing function call test ...");
for (int i = 0; i < _iteration; i++) {
CallFunction();
}
DateTime t2 = DateTime.Now;
Console.WriteLine(t2.ToString("mm:ss.ffff/t:") + "Test finished!");
TimeSpan deltaDelegate = t1 - t0;
TimeSpan deltaFunction = t2 - t1;
Console.WriteLine("Result:");
Console.WriteLine("/tTime expense (delegate : function) = {0} : {1} = {2:0.000}",
deltaDelegate.Ticks.ToString(),
deltaFunction.Ticks.ToString(),
(double)(deltaDelegate.Ticks) / (double)(deltaFunction.Ticks));
}
#endregion
}
// 比较调用单个函数时的效率
class SingleDelegateTester : DelegateTester
{
public SingleDelegateTester(int multicast, long iteration)
: base(multicast, iteration)
{
}
protected override void CallFunction()
{
this.WorkMethod();
}
}
/// <summary>
/// 测试多播方式的函数调用时的效率
/// </summary>
class MulticastDelegateTester : DelegateTester
{
IVoidMethod[] calls;
public MulticastDelegateTester(int multicast, long iteration)
: base(multicast, iteration)
{
calls = new IVoidMethod[multicast];
for (int i = 0; i < calls.Length; i++) {
calls[i] = this;
}
}
protected override void CallFunction()
{
foreach (IVoidMethod i in calls) {
i.WorkMethod();
}
}
}
public class Test
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
static void Main()
{
long iteration = 1000000000;
IDelegateTester[] tests = new IDelegateTester[]{
new SingleDelegateTester(1, iteration),
new MulticastDelegateTester(10, iteration)};
foreach (IDelegateTester t in tests) {
t.Run();
Console.WriteLine("Press any key to run next test...");
Console.ReadKey();
}
Console.WriteLine("Test finished, press any key to quit ...");
Console.ReadKey();
}
}
程序运行结果如下:
Delgate Test(Multicast=1, iteration=1000000000
07:49.2850 :Runing delegate test ...
08:01.3380 :Runing function call test ...
08:11.6500 :Test finished!
Result:
Time expense (delegate : function) = 120530000 : 103120000 = 1.169
Press any key to run next test...
Delgate Test(Multicast=10, iteration=1000000000
08:58.1870 :Runing delegate test ...
10:54.3060 :Runing function call test ...
12:36.5080 :Test finished!
Result:
Time expense (delegate : function) = 1161190000 : 1022020000 = 1.136
Press any key to run next test...
Test finished, press any key to quit ...
由以上运行结果可以看出,delegate方式的函数调用的确比直接调用函数效率要低很多,特别是只注册一个事件处理函数时效率差别非常明显,相差近20%,这在实时控制程序中还是应该注意的问题。而在多播时由于两种方式都需要对注册的事件列表进行遍历,使得性能差距拉近,但仍在13.6%,在对性能要求比较高的情况要真的要考虑是使用delegate的方便性还是自行实现多播方式的函数调用了。
注意上面为了不让条件判断语句影响测试的准确性,在调用delegate时把判空操作注释掉了,即使在这种情况下仍然效率不及直接调用,下面研究一下原因。
首先看一下Run()函数,两种调用方式的测试代码段分别如下:
(1)Delegate调用段
DateTime t0 = DateTime.Now;
00000095 lea ecx,[ebp-5Ch]
00000098 call 78639580
0000009d lea edi,[ebp-14h]
000000a0 lea esi,[ebp-5Ch]
000000a3 movq xmm0,mmword ptr [esi]
000000a7 movq mmword ptr [edi],xmm0
Console.WriteLine(t0.ToString("mm:ss.ffff/t:") + "Runing delegate test ...");
000000ab lea ecx,[ebp-14h]
000000ae mov edx,dword ptr ds:[023930A4h]
000000b4 call 78667010
000000b9 mov esi,eax
000000bb mov edx,dword ptr ds:[023930A8h]
000000c1 mov ecx,esi
000000c3 call 785FBDE0
000000c8 mov esi,eax
000000ca mov ecx,esi
000000cc call 78678544
for (int i = 0; i < _iteration; i++) {
000000d1 xor edx,edx
000000d3 mov dword ptr [ebp-18h],edx
000000d6 nop
000000d7 jmp 000000E8
CallDelegate();
000000d9 mov ecx,dword ptr [ebp+FFFFFF68h]
000000df call dword ptr ds:[0389007Ch]
for (int i = 0; i < _iteration; i++) {
000000e5 inc dword ptr [ebp-18h]
000000e8 mov eax,dword ptr [ebp-18h]
000000eb cdq
000000ec mov ecx,dword ptr [ebp+FFFFFF68h]
000000f2 cmp edx,dword ptr [ecx+8]
000000f5 jg 000000FE
000000f7 jl 000000D9
000000f9 cmp eax,dword ptr [ecx+4]
000000fc jb 000000D9
}
(2)函数直接调用ukw
DateTime t1 = DateTime.Now;
000000fe lea ecx,[ebp-64h]
00000101 call 78639580
00000106 lea edi,[ebp-20h]
00000109 lea esi,[ebp-64h]
0000010c movq xmm0,mmword ptr [esi]
00000110 movq mmword ptr [edi],xmm0
Console.WriteLine(t1.ToString("mm:ss.ffff/t:") + "Runing function call test ...");
00000114 lea ecx,[ebp-20h]
00000117 mov edx,dword ptr ds:[023930A4h]
0000011d call 78667010
00000122 mov esi,eax
00000124 mov edx,dword ptr ds:[023930ACh]
0000012a mov ecx,esi
0000012c call 785FBDE0
00000131 mov esi,eax
00000133 mov ecx,esi
00000135 call 78678544
for (int i = 0; i < _iteration; i++) {
0000013a xor edx,edx
0000013c mov dword ptr [ebp-24h],edx
0000013f nop
00000140 jmp 00000150
CallFunction();
00000142 mov ecx,dword ptr [ebp+FFFFFF68h]
00000148 mov eax,dword ptr [ecx]
0000014a call dword ptr [eax+38h]
for (int i = 0; i < _iteration; i++) {
0000014d inc dword ptr [ebp-24h]
00000150 mov eax,dword ptr [ebp-24h]
00000153 cdq
00000154 mov ecx,dword ptr [ebp+FFFFFF68h]
0000015a cmp edx,dword ptr [ecx+8]
0000015d jg 00000166
0000015f jl 00000142
00000161 cmp eax,dword ptr [ecx+4]
00000164 jb 00000142
}
但是从汇编代码看,这里调用CallFunction()时反而多了一条MOV指令,从这里看应该直接调用函数会比delegate调用更消耗资源,但这条指令是单周期指令消耗资源极少,先不去管它,其它代码都一样,看来性能差在CallFunction()和CallDelegate()两个函数上,且看这两个函数的汇编代码:
protected void CallDelegate()
{
//if (task != null)
{
task();
00000000 push esi
00000001 mov esi,ecx
00000003 cmp dword ptr ds:[00269204h],0
0000000a je 00000011
0000000c call 793B672F
00000011 mov ecx,dword ptr [esi+0Ch]
00000014 mov eax,dword ptr [ecx+0Ch]
00000017 mov ecx,dword ptr [ecx+4]
0000001a call eax
}
}
0000001c nop
0000001d pop esi
0000001e ret
protected override void CallFunction()
{
this.WorkMethod();
00000000 push esi
00000001 mov esi,ecx
00000003 cmp dword ptr ds:[00269204h],0
0000000a je 00000011
0000000c call 793B66D7
00000011 mov ecx,esi
00000013 mov eax,dword ptr [ecx]
00000015 call dword ptr [eax+3Ch]
}
00000018 nop
00000019 pop esi
0000001a ret
两段代码不同的地方如上面的黄色区域所示,在这里CallDelegate却又比CallFunction多执行一次MOV操作,这与调用这些函数前面那段结合起来应该一样,如果差别的话只能差在这几个MOV和CALL操作上了,在CallDelegate函数中三次MOV操作都是双字节赋值,并且是间接寻址到寄存器,而CallFunction的两次MOV操作只有一次是间接寻址,而另一次是从寄存器到寄存器,这要比从内存到寄存器快。看来差别也只是这个原因了。
下面看一下多播的情况。函数CallFunction代码如下:
protected override void CallFunction()
{
foreach (IVoidMethod i in calls) {
00000000 push edi
00000001 push esi
00000002 push ebx
00000003 push ebp
00000004 mov edi,ecx
00000006 cmp dword ptr ds:[00309204h],0
0000000d je 00000014
0000000f call 796C66A7
00000014 xor ebx,ebx
00000016 xor ebp,ebp
00000018 xor esi,esi
0000001a mov eax,dword ptr [edi+14h]
0000001d mov ebp,eax
0000001f xor esi,esi
00000021 nop
00000022 jmp 0000003D
00000024 cmp esi,dword ptr [ebp+4]
00000027 jb 0000002E
00000029 call 796C7CE3
0000002e mov eax,dword ptr [ebp+esi*4+0Ch]
00000032 mov ebx,eax
i.WorkMethod();
00000034 mov ecx,ebx
00000036 call dword ptr ds:[00310024h]
0000003c inc esi
foreach (IVoidMethod i in calls) {
0000003d cmp esi,dword ptr [ebp+4]
00000040 jl 00000024
}
}
00000042 nop
00000043 pop ebp
00000044 pop ebx
00000045 pop esi
00000046 pop edi
00000047 ret
而CallDelegate的代码并没有改变,代码差别很大,比较汇编比较困难。换条思路,研究一下delegate的实现机制
分享到:
相关推荐
C# Delegate讲解C# Delegate讲解C# Delegate讲解C# Delegate讲解
适合初学者了解C#的delegate,是一个非常简单的例子。
C# delegate thread范例 若不懂在代码中,线程如何调用的可以看看
本文实例讲述了C#使用委托(delegate)实现在两个form之间传递数据的方法。分享给大家供大家参考。具体分析如下: 关于Delegate【代理、委托】是C#中一个非常重要的概念,向前可以推演到C++的指针,向后可以延续到匿名...
C#中的事件和委托(Delegate,Event)
委托 和 事件在 .Net Framework中的应用非常广泛,然而,较好地理解委托和事件对很多接触C#时间不长的人来说并不容易。它们就像是一道槛儿,过了这个槛的人,觉得真是太容易了,而没有过去的人每次见到委托和事件就...
网上有很多关于C++ delegate机制的文章,但都讲的是函数指针的内容,上周就C++中实现C#的delegate机制的问题研究了好几天,查看了很多相关资料,才解决了问题,现将我写的C++ delegate测试程序贴出来,希望能帮到有...
本ppt结合场景和实例对C#委托(delegate)的基础进行了讲解:包括了委托的定义,基本语法,同步异步调用,匿名委托的使用,泛型委托,lamda表达式,剖析了委托协变、委托逆变。对委托在.net 3.5框架中的身影进行了分析...
c#中关于委托delegate的经典的实例,可以使我们清楚的了解delegate的使用方法及含义。
c#的回调函数(delegate关键字)
C#中有很多易混淆的关键词,例如delegate,Func, Action和 Predicate。Func, Action和 Predicate本质上都是delegate,下面看一下delegate概念。 1 delegate概念 delegate本质上就是一个指向函数的指针,可以指向...
CSharp_Delegate C#委托 本人博客中的示例代码
委托回调
最近收集的VB.Net-C#多线程Thread-代理委托delegate编程。文章列表: c#.net多线程同步.txt C#WebBrowser页面与WinForm交互技巧一.txt C#多线程编程-多细全.txt C#多线程编程简单实例.txt C#多线程窗体控件安全访问....
从序言中,大家应该对委托和事件的重要性有点了解了吧,虽然说我们现在还是能模糊,但是从我的大白话系列中,我会把这些概念说的通俗易懂的。首先,我们还是先说说委托吧,从字面上理解,只要是中国人应该都知道这个...
本篇文章是对C#中interface与delegate的效能比较进行了详细的分析介绍,需要的朋友参考下
delegate与事件的实例,很简单的例子,帮助初学者理解,其中有两个delegate,一个有参数,一个无参数
Context Delegate Source C# - C# Source code
主要介绍了C#委托delegate实例解析,对C#的委托做了较为深入的剖析,很适合初学者学习,需要的朋友可以参考下
在c#中,event与delegate是两个非常重要的概念。因为在Windows应用程序中,对事件的使用非常频繁,而事件的实现依赖于delegate。 下面是对网上一些比较好的关于delegage的资料的整理,以及自己的一些想法。 Delegate...