闲来无事,看到有争论c#事件机制效率的,小小地测试一番~
结论先放这里:
1.订阅者数量较少的情况下和接口列表访问不相伯仲,事件机制访问速度多次比列表快
2.订阅者较多的情况(百万数量级)下,触发事件消耗的时间是接口列表(遍历器)的1.3倍左右
3.接口列表用数组下标方式访问调用蜜汁效率,比遍历器访问快0.6左右;
4.事件有GC,列表在百万级数量没有GC,样本数量足够大的情况列表出现了蜜汁GC= =
5.事件订阅和取消订阅行为很慢,约40倍于列表增删接口;
6.本猿认为:一个正常程序在他每片生命周期中,即使是onUpdate事件,实际调用的函数不会超过2k的,能达到500就已经是比较大的程序了(从做游戏的角度想,一个场景放500个onUpdate订阅者?简直疯了,不论是用事件还是接口列表,被喷出翔是免不了的了)。相比芝麻绿豆的事件性能消耗,业务逻辑和项目管理更加重要(接口列表意味着要额外维护一个接口、一个列表、至少2个函数(add和remove),evnet则是封装完善线程安全)。简单点来说就是这个测试并没有什么用,根据自己项目需要采用适合的观察者模式实现才是正确的。
代码如下(随便写的,结果建议复制到notepad看看) ****本代码是以WarmUp为前提的,因为不这样做波动太大。无可否认jit对代码优化有巨大影响,实际项目环境可能不一样,仅供参考
HiPerfTimer.cs来自网络,已经忘了出处了。
class Program{static void Main(string[] args){Console.WriteLine("Hello World!");Stopwatch sw = new Stopwatch();HiPerfTimer p=new HiPerfTimer();int 样本大小 = 10000000;//data initList counters = new List();for (int i = 样本大小; i >= 0; i--){var c= new Counter();counters.Add(c);c.onUpdate();}//warmupforeach(var v in counters){v.onUpdate();}Console.WriteLine("样本ok");Console.ReadKey();sw.Restart();//eventsw.Start();p.Start();foreach (var v in counters){onUpdate += v.onUpdate;}p.Stop();sw.Stop();Console.WriteLine($"订阅事件耗时{sw.ElapsedTicks} - {p.Duration}");sw.Reset();sw.Start();p.Start();onUpdate();p.Stop();sw.Stop();Console.WriteLine($"发送事件消息(不判空)耗时{sw.ElapsedTicks} - {p.Duration}");sw.Reset();sw.Start();p.Start();onUpdate?.Invoke();p.Stop();sw.Stop();Console.WriteLine($"发送事件消息(判空)耗时{sw.ElapsedTicks} - {p.Duration}");sw.Reset();Action tar=counters[657].onUpdate;p.Start();onUpdate-=tar;p.Stop();Console.WriteLine($"移除一个事件耗时:{p.Duration}");//lstList counters1 = new List();sw.Start();p.Start();foreach (var v in counters){counters1.Add(v);}p.Stop();sw.Stop();Console.WriteLine($"记录回调耗时{sw.ElapsedTicks} - {p.Duration}");sw.Reset();sw.Start();p.Start();foreach (var v in counters1){v.onUpdate();}p.Stop();sw.Stop();Console.WriteLine($"回调耗时{sw.ElapsedTicks} - {p.Duration}");sw.Reset();sw.Start();p.Start();for(int i = 0; i < 样本大小; i++){counters1[i].onUpdate();}p.Stop();sw.Stop();Console.WriteLine($"回调耗时(i) {sw.ElapsedTicks} - {p.Duration}");sw.Reset();var tar2=counters[457];p.Start();counters1.Remove(tar2);p.Stop();Console.WriteLine($"移除事件耗时{p.Duration}");sw.Reset();var ary = counters.ToArray();sw.Start();p.Start();foreach (var v in ary){v.onUpdate();}p.Stop();sw.Stop();Console.WriteLine($"数组回调耗时{sw.ElapsedTicks} - {p.Duration}");sw.Reset();sw.Start();p.Start();for (int i = 0; i < ary.Length; i++){ary[i].onUpdate();}p.Stop();sw.Stop();Console.WriteLine($"数组回调(i)耗时{sw.ElapsedTicks} - {p.Duration}");Console.ReadLine();}public static event Action onUpdate;}class Counter{public int i = 0;public void onUpdate(){i++;}}