测试虚拟函数的开销

发布于 2024-09-07 15:04:35 字数 1280 浏览 4 评论 0原文

我设置了一个(可能非常不科学)小测试来确定一级单继承中虚拟函数的开销,并且当多态访问派生类或直接访问派生类时,我得到的结果完全相同。令人有点惊讶的是,当任何函数被声明为虚拟时,所引入的计算时间的数量级(参见下面的结果)。

声明成员函数时是否有这么多开销,为什么即使直接访问派生类,它仍然存在?

代码如下:

class base
{
public:
    virtual ~base() {}
    virtual uint func(uint i) = 0;
};

class derived : public base
{
public:
    ~derived() {}
    uint func(uint i) { return i * 2; }
};

uint j = 0;
ulong k = 0;
double l = 0;
ushort numIters = 10;
base* mybase = new derived;  // or derived* myderived = ...

for(ushort i = 0; i < numIters; i++)
{
  clock_t start2, finish2;
  start2 = clock();

  for (uint j = 0; j < 100000000; ++j)
        k += mybase->func(j);

  finish2 = clock();
  l += (double) (finish2 - start2);
  std::cout << "Total duration: " << (double) (finish2 - start2) << " ms." << std::endl;

}

std::cout << "Making sure the loop is not optimized to nothing: " << k << std::endl;
std::cout << "Average duration: " << l / numIters << " ms." << std::endl;

结果:

base* mybase = new returned; 平均约为 338 毫秒。

衍生* my衍生 = 新衍生; 平均约为 338 毫秒。

消除继承和删除虚拟函数平均需要约 38 毫秒。

这几乎减少了 10 倍!所以基本上,如果任何函数被声明为虚拟,即使我不以多态方式使用它,开销也将始终相同?

谢谢。

I set up a (perhaps very unscientific) small test to determine the overhead of virtual functions in a one-level single inheritance and the results I got were, well, exactly the same when accessing the derived class polymorphically or when accessing it directly. What was a bit surprising was the order of magnitude of computation time that is introduced when any function is declared virtual (see results below).

Is there so much overhead when declaring member functions as such, and why is it still present even when accessing the derived class directly?

The code is as follows:

class base
{
public:
    virtual ~base() {}
    virtual uint func(uint i) = 0;
};

class derived : public base
{
public:
    ~derived() {}
    uint func(uint i) { return i * 2; }
};

uint j = 0;
ulong k = 0;
double l = 0;
ushort numIters = 10;
base* mybase = new derived;  // or derived* myderived = ...

for(ushort i = 0; i < numIters; i++)
{
  clock_t start2, finish2;
  start2 = clock();

  for (uint j = 0; j < 100000000; ++j)
        k += mybase->func(j);

  finish2 = clock();
  l += (double) (finish2 - start2);
  std::cout << "Total duration: " << (double) (finish2 - start2) << " ms." << std::endl;

}

std::cout << "Making sure the loop is not optimized to nothing: " << k << std::endl;
std::cout << "Average duration: " << l / numIters << " ms." << std::endl;

Results:

base* mybase = new derived; gives an average of ~338 ms.

derived* myderived = new derived; gives an average of ~338 ms.

Eliminating inheritance and removing virtual functions gives an average of ~38 ms.

That's almost 10 times less! So basically, if any function is declared virtual the overhead will always be identically present, even if I don't use it polymorphically?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

凉墨 2024-09-14 15:04:35

“直接”访问它与“间接”访问它执行相同的工作。

当您调用 my衍生 上的函数时,存储在那里的指针可能指向从 衍生 派生的某个类的某个对象。编译器不能假设它确实是一个派生对象,它可能是重写虚拟函数的进一步派生类的对象,因此需要像 <代码>mybase案例。在这两种情况下,函数在调用之前都会在虚函数表中查找。

要以非多态方式调用函数,不要使用指针:

derived myderived;
myderived.func(1); 

当您删除虚拟函数时,编译器可以内联函数调用,这样您基本上就会得到一个简单的循环:

for (uint j = 0; j < 100000000; ++j)
    k += i * 2;

这要快得多,因为您节省了100000000 次函数调用,编译器甚至可能能够进一步优化循环,而如果其中有函数调用则不会。

另请注意,如果函数做了一些实际工作,内联版本和虚拟函数调用之间的差异就会小得多。在这个例子中,函数体几乎不花费任何时间,因此调用函数的成本超过了执行函数体的成本。

Accessing it "directly" is doing the same work as accessing it "indirectly".

When you call the function on myderived, the pointer stored there could point to some object of some class derived from derived. The compiler can't assume that it really is a derived object, it might be an object of a further derived class that overrides the virtual function, so there needs to be virtual function dispatch just like in the mybase case. In both cases the function is looked up in the virtual function table before it is called.

To call the function non-polymorphically, don't use a pointer:

derived myderived;
myderived.func(1); 

When you remove the virtual functions, the compiler can inline the function call so that you basically end up with a simple loop:

for (uint j = 0; j < 100000000; ++j)
    k += i * 2;

This is much faster since you save the overhead of 100000000 function calls and the compiler might even be able to optimize the loop further in ways it wouldn't if there was a function call in it.

Note also that the difference between the inlined version and the virtual function call would be much less if the function did some real work. In this example the function body takes almost no time at all, so the costs for calling the function outweigh the costs for executing the body.

楠木可依 2024-09-14 15:04:35

虚拟功能基本上不需要任何成本。大多数真正的性能问题都是由不必要的繁琐的调用树造成的,这些调用树做了一些你永远不会想到会出现问题的事情。

我找到它们的方法是在调试器下多次暂停应用程序,并检查状态,包括调用堆栈。 下面是使用该方法获得 43 倍加速的示例

Virtual functions cost essentially nothing. Most real performance issues are caused by needlessly bushy call trees doing things you would never guess to be a problem.

The way I find them is by pausing the app several times under the debugger, and examining the state, including the call stack. Here's an example of using that method to get a 43x speedup.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文