实现闭包和性能影响,例如相对与绝对跳跃

问题描述 投票:0回答:1

不久前,我在编译器类中学习了闭包转换,并且想知道调用闭包与调用函数相比会产生多少性能开销。

考虑一个函数返回一个带有零个参数的闭包(因此没有变量捕获),唯一的区别是调用所述闭包将跳转到绝对地址,而不是调用被翻译为的函数的相对地址一个标签。

与相对跳转相比,绝对跳转是否有任何性能开销?例如,在 ARM64 处理器中,

bx Rm
b label
慢吗?

假设两种情况下跳转到的位置都已被缓存。

arm x86-64 cpu-architecture compiler-optimization
1个回答
2
投票

我们可以查看函数指针、lambda、闭包来了解闭包的一些定义。我们可以看到闭包允许访问调用者中的变量。在 pascal 中,您可以按引用或按值传递。 C++ 中的“&references”也是如此。这是针对单个值的。使用闭包,调用者的整个范围都是可访问的,但它是由实现定义的(需要哪些外部引用)。

我们可以以 C++ 中的“vtable”为例,它负责实现基类的继承重写。 “vtable”是从 this 指针引用的函数指针表。派生对象将具有一个 vtable,其条目与基类不同。

您提供的示例汇编程序是

bx Rm
b label
。这确实更像是C++虚方法的区别。虚拟方法通常使用
bx Rm
来调度,而不是直接调用。速度减慢是最小的,您可以找到各种对“C++ 虚拟函数开销”的引用。

想知道调用闭包与调用函数相比有多少性能开销?

如果闭包可以捆绑,只需要作为数据指针传递即可。所以对变量的访问就像

ldr Rn, [Rclosure, #variable]
。然后可以像按值传递一样使用“Rn”。通常,函数需要堆栈帧,特别是在寄存器压力很大的情况下。我想象生成汇编程序的语言会在调用者中安排函数“序言”,并且它将设置一个堆栈帧和/或一个允许访问调用者变量的变量“vtable”。就像“C++ 虚函数开销”一样,闭包引用应该处于相同的顺序(即非常非常小)。

当例程完成时,当然还有一个额外的存储来更新调用者的值。

stm Rclosure, {reglist}
,在某些情况下可能是可能的和/或编译器可以进行值分析/SSA 并决定在任何更新“完成”时存储值。因此,它很可能是每个使用的闭包变量的加载/存储周期。


OP有一个观点,

bx Rm
可能相关。 “thunk”可以是通过闭包代码将闭包数据编组为公共布局。然后它会在返回时写回数据。

这在一定程度上取决于您对调用者和被调用者/thunk 付出的努力。如果调用者可以强制执行某些标准布局,那么您可以直接在闭包中使用该数据。这在实践中可能几乎是不可能的,因此可能会使用“thunk”来整理数据。

这里的开销是2N*加载/存储+虚拟调度。我们需要将来自调用者的数据编组到序言中的闭包布局。然后,thunk 中的尾声将需要将数据封送回来。每个编组操作都需要“n”(引用变量)来加载/存储。

另一种替代方法是通过数据指针访问所有变量。在这里,数据只需要放置在一个公用表中即可访问“n”个变量。然后由被调用者将值缓存在寄存器中和/或根据需要写回。

例如。类似“C”的表示法。

 enum {char *, short *, int *} references;
 closure(references data[n], arg1, arg2, ...);

Thunk 的优点是可以动态创建。即,调用者不需要有关闭包实现的信息,它们可以在链接时生成。

这将是全局编译(lto)或模块构建然后链接(传统编译)之间的设计选择。最有可能使用数据指针方法,因为它最大限度地减少了耦合,并且只需要修改一点对象信息即可实现 thunk 生成。您需要额外的基础设施来实现类型安全,这可以通过 mangling、rtti 或许多其他机制来完成。

© www.soinside.com 2019 - 2024. All rights reserved.