在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引用函数。

在这一点上,你有你所需要的。 您可以传递这些记录并使用它们提供的间接编写通用测试代码。

尽管如此,这种间接性会付出代价 毕竟,你在这里是手动实现接口。 预计函数调用的性能开销与接口类似。

我预计,除非您的操作数是大型数组,否则间接成本会偏离您的基准。 我知道你具体问过如何使用间接方式来实现测试,但我个人会想尽可能使用尽可能接近真实代码的方式进行测试。 这意味着要测试直接函数调用。


你问泛型。 他们对你没用。 为了创建一个在被测试类上被参数化的泛型类,你需要被测试的类从一个公共基类派生,或者实现一个通用接口。 然后你回到你开始的地方。


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

如果你看看这个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本机代码更快,适用于浮点。 你会获得很多开发时间。


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;
链接地址: http://www.djcxy.com/p/35019.html

上一篇: Simulating virtual methods in Delphi

下一篇: Delphi XE3 Invalid Pointer when trying to free FSQL (TStringList)