在Delphi中模拟虚拟方法

时间:2014-11-15 08:39:31

标签: 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;

3 个答案:

答案 0 :(得分:2)

您希望实现看起来像虚拟方法但出于性能原因而不使用虚方法或接口的内容。

您需要添加一些间接。创建一个包含过程变量的记录。例如:

type
  TAddFunc = function(a, b: Double): Double;

  TMyRecord = record
    AddFunc: TAddFunc;
  end;

然后声明记录的两个实例。一个填充了SSE功能,另一个填充了非SSE参考功能。

此时你有你需要的东西。您可以传递这些记录并使用它们提供的间接来编写通用测试代码。

这种间接将花费成本。毕竟,你在这里有一个接口的手动实现。预期函数调用的性能开销与接口类似。

我希望,除非您的操作数是大型数组,否则间接成本会使您的基准测试产生偏差。我知道您具体询问了如何使用间接实现测试,但我个人希望尽可能接近真实代码进行测试。这意味着测试直接函数调用。


你问泛型。它们对你没用。为了使得在被测试的类上进行参数化的泛型类,您需要从公共基类派生测试类,或者实现公共接口。然后你就回到了你开始的地方。

答案 1 :(得分:1)

在您的代码中,主要的速度差异不会在函数调用之间。

如果你看看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 engineDelphi Web Script。 Besen复制asm存根以创建JITted缓冲区,而DWS通过一组生成器方法计算操作码。

如果您需要浮点性能,还可以考虑使用具有调优和优化JIT的语言。你可以用例如我们的开源SpiderMonkey library for Delphi。您可以使用纯JavaScript编写代码,然后让优化的JIT完成其工作。您可能会对结果速度感到惊讶:对于浮点,结果为usually faster than Delphi x87 native code。你会获得很多开发时间。

答案 2 :(得分:0)

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;