动态调度

✍ dations ◷ 2025-09-19 08:17:49 #方法 (计算机科学)

在计算机科学中,动态调度(Dynamic dispatch)是指运行时选择哪一个多态操作的实现(方法或函数)来调用的过程。动态调度通常被应用于面向对象编程(OOP)的语言和系统,并被认为是一个主要特点。

面向对象的系统把一个问题看作是一系列通过名字引用来制定操作的相互影响的物体。多态性是指一些可互换的物体虽有相同名字但却在行为上不同的现象。例如,一个文件对象和一个数据库对象都有一个储存记录的方法来记录需要存储的的个人记录,但是二者的实现却不同。一个程序可以有对文件或数据库的访问。当一个程序调用对象的存储记录时,有一些东西需要来决定采取哪种行为。如果有人认为OOP仅仅指给对象发送信息,那么在这个例子中程序仅仅把一条存储记录信息发送给了一个未知类型的对象,而把如何将这条信息发送给正确对象交给运行支持系统来处理。这个对象来决定它执行哪些行为。

与动态调度成对比的是静态调度,在静态调度中,对一个多态操作的实现是在编译时间就选择好的。动态调度的目的在于支持那些当在编译时间内无法决定一个多态操作的合适的实现因为这个决定取决于这个操作的一个或多个实际参数的运行类型的情形。

动态调度和动态绑定不同。在选择操作的上下文中,绑定把名称和操作相关联,而调度则在确定名称所引用的操作之后选择操作的实现。通过使用动态调度,名称可以在编译时被绑定到多态操纵中,但是直到运行时才执行该实现。虽然动态分派不暗示后期绑定,但后期绑定意味着动态分派,因为绑定决定了什么可以调度。

选择调用方法的具体版本的决定可以基于单个对象或者对象的组合。前者被称为单一调度,并直接由常见的面向对象语言如C++,Java,Smalltalk、Objective-C、Swift、JavaScript和Python所支持。在这些和与其相类似的语言中,我们可以调用类似于语法的划分的方法。其参数是可选的。这被认为是发送一个名为divide,带有除数参数的信息来做除法。

相比之下,一些语言(例如Common Lisp、Dylan、Julia)采用基于操作数的组合的调度方法或函数;在除法情况下,被除数和除数的类型一起确定将执行哪个除法运算。这就叫做多重调度。

语言可以用不同的动态调度机制来实现。 由语言提供的动态调度机制的选择在很大程度上改变了在给定语言内可用或最自然使用的编程范例。

通常,在类型语言中,会基于参数的类型(最常见地基于消息的接收者的类型)来执行调度机制。 这可能被称为“单类型动态调度”。具有较弱或无打字系统的语言通常携带调度表作为每个对象的对象数据的一部分。这允许实例行为因为每个实例可以将给定消息映射到单独的方法。而有些语言提供混合方法。

动态调度总会产生开销,因此一些语言为特定方法提供静态调度。

C ++使用早期绑定,并提供动态和静态调度。 调度的默认形式是静态的。 为了获得动态分派,程序员必须将一个方法声明为virtual。

C ++编译器通常使用称为虚拟表(vtable)的数据结构来实现动态调度,该表定义给定类(C ++本身没有vtable的概念)的消息到方法映射。 然后,该类型的实例将存储指向此表的指针作为其实例数据的一部分。 当使用多重继承时,这是复杂的因为C ++不支持后期绑定,C ++对象中的虚拟表不能在运行时修改,这将调度目标的潜在集限制为在编译时选择的有限集。

类型重载不会在C ++中产生动态分派,因为语言将消息参数的类型视为正式消息名称的一部分。 这意味着程序员看到的消息名不是用于绑定的正式名称。

在Go和Rust中使用了一种更多样化的早期绑定。 Vtable指针会携带对象引用作为一种'fat指针'(go中的接口,或Rust中的'trait对象')。

这使得支持的接口与底层数据结构分离。 为了正确使用类型,每个编译库不需要知道所有的接口范围,只需要知道他们所需要的具体vtable布局即可。 代码可以将不同接口的同一块数据传递给不同的函数。 这种多功能性以每个对象引用的额外数据为代价,如果许多这样的引用被持久存储会出问题。

Smalltalk使用基于类型的消息分派器。每个实例都有一个类型,其定义包含方法。当实例接收到消息时,调度器在消息到方法映射中查找该类型的相应方法,然后调用该方法。

因为一个类型可以有一个基本类型链,这个查找可能是非常昂贵的。 一个不成熟的Smalltalk机制的实现似乎比C ++有更高的开销,并且这种开销将会发生在对象接收的每个消息里。

真正的Smalltalk实现通常使用一种称为内联缓存的技术,这种技术使得方法调度非常迅速。内联缓存基本上存储调用站点的前一个目标方法地址和对象类(或者多对缓存的多个对)。缓存方法使用最常见的基于方法选择器的目标方法(或仅缓存缺失处理程序)来进行初始化。当在执行期间到达方法调用站点时,它只调用缓存中的地址。(在动态代码生成器中,此调用是直接调用,因为直接地址由缓存缺失逻辑负责回填)。被调用方法中的前序代码将缓存的类与实际对象类进行比较,如果它们不匹配,程序到缓存缺失处理程序分支中寻找正确的方法。一个快速实现可以具有多个高速缓存条目,并且它通常仅需要几个指令就可以在初始高速缓存未命中时执行正确的方法。常见的情况是一个缓存的类匹配,并且执行只会在该方法中继续。

外部缓存也可以通过使用对象类和方法选择器来实现在方法调用逻辑中的使用。在一个设计中,类和方法选择器被哈希并且用作到方法分派高速缓存表中的索引。

由于Smalltalk是一种反射语言,许多实现允许使用动态生成方法查找表的方法将单个对象变成对象。这允许在每个对象的基础上更改对象行为。一个被称为基于原型语言的语言类别已经从这里生根发芽,其中最着名的是Self和JavaScript。方法调度缓存的细心的设计甚至允许基于原型的语言具有高性能调度方法。

许多其他动态类型的语言,包括Python、Ruby、Objective-C和Groovy都使用类似的方法。

2.Driesen, Karel; Hölzle, Urs; Vitek, Jan (1995). . ECOOP. 

3.Müller, Martin (1995).  (Master thesis). University of New Mexico. pp. 16–17.

相关

  • 2-甲基-5-羟色胺2-甲基-5-羟色胺(英语:2-Methyl-5-hydroxytryptamine或2-Methylserotonin、2-Methyl-5-HT)是一种色胺衍生物与神经递质血清素密切相关。
  • 蝎王一世蝎王一世是传说中古埃及前王朝时期上埃及的开国君主,早于那尔迈150多年,定都于提尼斯。他的名字可能参考于古埃及女神塞尔凯特,巴勒莫石碑中简要地提及了他。蝎王一世的坟墓位
  • 黄麻圆果黄麻 C. capsularis 长果黄麻 C. olitorius黄麻(拉丁语:Corchorus)是一种长而柔软的、发出光泽的植物纤维,可以织成高强度的粗糙的细丝。它在植物分类上属于椴树亚科的黄麻
  • 2-丁烯酸巴豆酸即2-丁烯酸,结构式CH3CH=CHCOOH。巴豆酸在室温下为单斜晶系针状或棱状结晶。有顺式和反式两种双键异构体,通常为反式异构体。顺式与60%硫酸、微量盐酸或溴化氢共热,可转化
  • 肯恩大学肯恩大学(Kean University /ˈkeɪn/)是一所位于美国新泽西州联合镇和希尔塞德的公立大学,主要教授文科课程,2019年《美国新闻与世界报道》将其列在“北部地区大学”排名中的第1
  • 刘悟刘悟(?-825年),范阳(今北京、保定一带)人。唐朝平卢节度使李师道部将,后发动兵变杀师道,投降朝廷,封为昭义节度使。祖父刘正臣,原名刘客奴。刘悟少有勇力,效力宣武节度使刘逸准麾下,因偷
  • 2019冠状病毒病黎巴嫩疫情2019冠状病毒病黎巴嫩疫情,介绍在2019新型冠状病毒疫情中,在黎巴嫩发生的情况。2020年2月21日,黎巴嫩确诊首例病例,一名来自伊朗库姆的45岁女性病毒检测呈阳性,被转移到贝鲁特一
  • 锗的同位素锗(Ge,原子量:72.630(8))共有45个同位素,其中有4个同位素是稳定的。备注:画上#号的数据代表没有经过实验的证明,只是理论推测而已,而用括号括起来的代表数据不确定性。
  • 互联网控制消息协议互联网控制消息协议(英语:Internet Control Message Protocol,缩写:ICMP)是互联网协议族的核心协议之一。它用于网际协议(IP)中发送控制消息,提供可能发生在通信环境中的各种问题反
  • 赵锡江赵锡江(1929—),男,汉族,河北省唐山市乐亭县古河乡大捞鱼庄村人。中国人民解放军海军少将,中国人民解放军海军大连舰艇学院原政委。赵锡江1944年4月参加八路军,1945年11月加入中国