提问人:Anton Duzenko 提问时间:11/15/2014 最后编辑:Anton Duzenko 更新时间:11/17/2014 访问量:803
在 Delphi 中模拟虚拟方法
Simulating virtual methods in Delphi
问:
我正在为 SSE 指令编写一个 Delphi 接口。它是一个类(为了可见性等)TSimdCpu,具有N类方法(每个SSE指令一个;明显的性能开销现在不是问题)。
现在我想将我的代码的性能(虽然很慢)与做同样事情的纯 pascal 代码进行比较。我的第一个猜测是编写一个具有相同方法名称的类似类 TGenericCpu。但是,如果没有通用的基类和虚拟方法,我就不能只有一段测试代码来调用它应该运行测试的任何类的方法。 理想情况下,我想要这样的东西
TestOn(TSimdCpu);
TestOn(TGenericCpu);
但是我迷茫于如何在不使用 delphi 的虚拟方法的情况下实现这一点。我不想退回到虚拟方法,原因有两个:一个是性能,另一个是它只会用于测试,而对于所有实际用途,它会增加毫无意义的复杂性。
泛型在这里有用吗?类似的东西
TTest<T> = class
...
T.AddVector(v);
...
TTest<TSimdCpu>.Test;
TTest<TGenericCpu>.Test;
答:
您想要实现看起来像虚拟方法但出于性能原因不使用虚拟方法或接口的内容。
您需要添加一些间接内容。创建保存过程变量的记录。举例说明:
type
TAddFunc = function(a, b: Double): Double;
TMyRecord = record
AddFunc: TAddFunc;
end;
然后声明记录的两个实例。一个填充了 SSE 函数,另一个填充了非 SSE 引用函数。
在这一点上,你有你需要的东西。您可以传递这些记录,并使用它们提供的间接来编写通用测试代码。
不过,这种间接性会付出代价。毕竟,您在这里拥有的是接口的手动实现。预计函数调用的性能开销与接口的性能开销相似。
我预计,除非你的操作数是大型数组,否则间接成本会扭曲你的基准。我知道您特别询问了如何使用间接实现测试,但我个人希望使用尽可能接近真实代码进行测试。这意味着测试直接函数调用。
你问的是泛型。它们对你没有用。为了创建在被测类上参数化的泛型类,您需要从公共基类派生受测类,或实现公共接口。然后你又回到了你开始的地方。
评论
class procedure
procedure
大卫·赫弗南(David Heffernan)的想法似乎是目前唯一的方法。我做了一个快速测试 - 这是结果:
simd 516 ms (pointer to a function, asm)
JensG 1187 ms (virtual method, asm)
generic 2797 ms (pointer to a function, pascal)
generic virtual 3360 ms (virtual method, pascal)
对于 pascal 代码,普通函数调用和虚函数调用之间的差异可能相对较小,但对于 asm 则不然
if cpu = nil then
if test.name = 'JensG' then
for i := 1 to N do begin
form1.JensGAdd(v1^);
form1.JensGMul(v2^);
end
else
for i := 1 to N do begin
form1.GenericAdd(v1^);
form1.GenericMul(v2^);
end
else
for i := 1 to N do begin
cpu.AddVector(v1^);
cpu.MulVector(v2^);
end;
评论
在代码中,函数调用之间的主要速度差异不会是函数调用之间的差异。
如果你看一下 asm,虚拟方法调用是这样的
mov eax,object
mov ebx,[eax] // get the the class info VMT
call dword ptr [ebx+##] // where ## is the virtual method offset
而非虚拟方法是
mov eax,object
call SomeAbsoluteAddress
对于指向函数的指针(在堆栈上)
mov eax,object
call dword ptr [ebp+##] // where ## is the pointer in the stack
您只是在类信息 VMT 中获得一两次查找。
我怀疑您的测试针对指向函数的指针进行了过度优化,因为指针可能在堆栈上。在实际代码中,您必须将指针存储在某个位置,因此与虚拟方法调用相比,您将一无所获。
如果你将你的方法定义为 而不是 ,我怀疑类虚拟方法和函数重定向将执行完全相同:class procedure
procedure
mov eax,classinfo
call dword ptr [eax+##] // where ## is the virtual method offset
对于这样的计算,真正加快进程的可能根本不是调用函数,而是创建某种简单的 JIT。在运行函数之前,通过查看 asm 操作码创建二进制操作码流,然后创建一个包含执行流的缓冲区,并直接执行它。在这里,我们将讨论性能。它类似于内联函数调用。
我知道至少有两个(最近和维护的)项目使用Delphi编写的JIT编译:Besen JavaScript引擎和Delphi Web Script。Besen 复制 asm 存根以创建 JITted 缓冲区,而 DWS 通过一组生成器方法计算操作码。
如果需要浮点性能,还可以考虑使用具有优化和优化 JIT 的语言。例如,您可以使用我们的 Delphi 开源 SpiderMonkey 库。你可以用纯 JavaScript 编写代码,然后让优化的 JIT 完成它的工作。您可能会对生成的速度感到惊讶:对于浮点,结果通常比 Delphi x87 原生代码更快。您将获得大量的开发时间。
评论