在 C# 中优化多调度通知算法?
Posted
技术标签:
【中文标题】在 C# 中优化多调度通知算法?【英文标题】:Optimizing multiple dispatch notification algorithm in C#? 【发布时间】:2011-01-22 23:58:33 【问题描述】:抱歉标题,我想不出更好的方式来描述这个问题。基本上,我正在尝试在游戏中实现碰撞系统。我希望能够注册一个“碰撞处理程序”来处理可以转换为特定类型的两个对象(以任意顺序给出)的任何碰撞。因此,如果Player : Ship : Entity
和Laser : Particle : Entity
以及(Ship, Particle)
和(Laser, Entity)
的处理程序已注册,而不是(Laser, Player)
的冲突,则应通知两个处理程序,参数顺序正确,@ 冲突987654326@ 应该只通知第二个处理程序。
一个代码 sn-p 说一千个单词,所以这就是我现在正在做的事情(天真的方法):
public IObservable<Collision<T1, T2>> onCollisionsOf<T1, T2>()
where T1 : Entity
where T2 : Entity
Type t1 = typeof(T1);
Type t2 = typeof(T2);
Subject<Collision<T1, T2>> obs = new Subject<Collision<T1, T2>>();
_onCollisionInternal += delegate(Entity obj1, Entity obj2)
if (t1.IsAssignableFrom(obj1.GetType()) && t2.IsAssignableFrom(obj2.GetType()))
obs.OnNext(new Collision<T1, T2>((T1) obj1, (T2) obj2));
else if (t1.IsAssignableFrom(obj2.GetType()) && t2.IsAssignableFrom(obj1.GetType()))
obs.OnNext(new Collision<T1, T2>((T1) obj2, (T2) obj1));
;
return obs;
但是,这种方法很慢(可测量;我在实施后损失了大约 2 FPS),所以我正在寻找一种方法来减少几个周期/分配。
我想过(例如,花了一个小时实现然后因为自己是个白痴而把我的头撞到墙上)一种方法,该方法根据它们的哈希码将类型按顺序排列,然后将它们放入字典中,每个条目都是该类型对的处理程序的链接列表,并带有一个布尔值指示处理程序是否希望反转参数的顺序。不幸的是,这不适用于派生类型,因为如果传入派生类型,它不会通知基类型的订阅者。谁能想到比检查每个类型对(两次)以查看它是否匹配更好的方法?
谢谢, 罗伯特
【问题讨论】:
both handlers should be notified, with the arguments in the correct order
如果您希望通知两个处理程序,则需要检查两个处理程序(无论如何,如果您要进行任何检查),因此您可能不能只删除“两次”的要求,而无需更改其他内容。不过,您的代码似乎仍然无法满足此要求...?
@Tanzelax - 如果您为每个处理程序调用一次 OnCollisionsOf 方法,那么实际上(在此系统下),每个处理程序都会收到通知(因为将添加两个委托)。
【参考方案1】:
所有这些反思都不会很快。注意反射是如何在每次碰撞时发生的。这就是问题所在。
一些想法。
第一个想法:访问者模式。
在 OO 语言中实现相当快速的双虚拟调度的标准方法是构建访问者模式的实现。
你能否实现一个访问者模式,让访问者中的每个接受者都维护一个“在这种情况下要做的事情”列表?然后,您的 adder 方法包括确定编写新“要做的事情”的正确位置,然后将委托添加到该事物。当冲突发生时,你启动访问者进行双重调度,找到要做的事情的委托,并调用它。
您需要的不仅仅是双重派送吗?高效的多虚拟调度是可行的,但并不完全简单。
思路二:动态调度
C# 4 具有动态调度。它的工作方式是我们在第一次遇到调用站点时使用反射来分析调用站点。然后我们动态生成新的 IL 来执行调用,并缓存 IL。在第二次调用中,我们反思参数以查看它们是否与之前的类型完全相同。如果是,我们就重新使用现有的 IL 并调用它。如果不是,我们再次进行分析。如果参数通常只有几种类型,那么缓存很快就会开始只有命中而不是未命中,并且考虑到所有因素,性能实际上都非常好。肯定比每次都进行大量反射要快。我们每次做的唯一反思就是分析参数的运行时类型。
思路三:实现自己的动态调度
DLR 所做的事情并没有什么神奇之处。它进行一次分析,吐出一些 IL 并缓存结果。我怀疑你的痛苦正在发生,因为你每次都在重新进行分析。
【讨论】:
我选择推出我自己的类似动态调度的系统(它不会生成任何运行时 IL,但无论如何它似乎要快得多)。谢谢!【参考方案2】:如果每个Entity
都有一个标识其实体类型的属性,而您只是获取该属性的值,那不是更快吗?
然后,如果您在处理程序中对实体的顺序有一个约定,那么 Laser 将始终位于玩家之前,等等。
您的实体类型可以是和枚举,枚举顺序可以是您的处理程序对象顺序。
public IObservable<Collision<T1, T2>> onCollisionsOf<T1, T2>()
where T1 : Entity
where T2 : Entity
EntityTypeEnum t1 = T1.EntityType;
EntityTypeEnum t2 = T2.EntityType;
Subject<Collision<T1, T2>> obs = new Subject<Collision<T1, T2>>();
_onCollisionInternal += delegate(Entity obj1, Entity obj2)
if (t1 < t2)
obs.OnNext(new Collision<T1, T2>((T1) obj1, (T2) obj2));
else
obs.OnNext(new Collision<T1, T2>((T1) obj2, (T2) obj1));
;
return obs;
【讨论】:
这似乎根本没有解决调度问题,只是为类型提供了严格的排序(这已经可以通过使用 obj.GetType().GetHashCode())。它也不允许注册基类型对象的冲突。【参考方案3】:我假设您要进行 2 组碰撞:激光/播放器和激光/激光。如果你愿意让 IObservable> > 只匹配这两种情况,那么你就可以将委托减少到只进行一次检查,并将所有内容强类型化以进行比较。
_onCollisionInternal += delegate(T1 obj1, T2 obj2)
obs.OnNext(new Collision<T1, T2>( obj1, obj2));
;
public IObservable<Collision<T1, T2>> onCollisionsOf<T1, T2>()
where T1 : Entity
where T2 : Entity
Subject<Collision<T1, T2>> obs = new Subject<Collision<T1, T2>>();
_onCollisionInternal += delegate(T1 obj1, T2 obj2)
obs.OnNext(new Collision<T1, T2>( obj1, obj2));
;
return obs;
Observable<Collision<Laser, Player>> lp = onCollisionsOf<Laser, Player>();
Observable<Collision<Laser, Laser>> ll = onCollisionsOf<Laser, Laser>();
【讨论】:
不,还有更多的碰撞类型。我正在寻找通用的东西——AFAICT,它不是独立于顺序的,但是我的碰撞系统不做任何顺序保证。以上是关于在 C# 中优化多调度通知算法?的主要内容,如果未能解决你的问题,请参考以下文章
优化调度基于matlab多目标粒子群算法求解风电光伏储能电网发电与需求响应调度优化问题含Matlab源码 239期