使用 C++ 中的接口会导致性能损失吗?

发布于 2024-07-05 00:09:00 字数 37 浏览 16 评论 0原文

在 C++ 中使用接口(抽象基类)时是否存在运行时性能损失?

Is there a runtime performance penalty when using interfaces (abstract base classes) in C++?

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

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

发布评论

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

评论(16

紙鸢 2024-07-12 00:09:00

应该注意的一件事是,虚拟函数调用成本可能因平台而异。 在控制台上,它们可能更明显,因为通常 vtable 调用意味着缓存未命中,并且可能会破坏分支预测。

One thing that should be noted is that virtual function call cost can vary from one platform to another. On consoles they may be more noticeable, as usually vtable call means a cache miss and can screw branch prediction.

彼岸花似海 2024-07-12 00:09:00

在 C++ 中使用抽象基类通常要求使用虚函数表,所有接口调用都将通过该表查找。 与原始函数调用相比,成本很小,因此在担心它之前,请确保您需要比这更快。

Using abstract base classes in C++ generally mandates the use of a virtual function table, all your interface calls are going to be looked up through that table. The cost is tiny compared to a raw function call, so be sure that you need to be going faster than that before worrying about it.

风流物 2024-07-12 00:09:00

我所知道的唯一主要区别是,由于您没有使用具体的类,因此内联(更难?)更难做到。

The only main difference I know of is that, since you're not using a concrete class, inlining is (much?) harder to do.

芸娘子的小脾气 2024-07-12 00:09:00

我唯一能想到的是,虚拟方法的调用比非虚拟方法要慢一点,因为调用必须经过 虚拟方法表

然而,这是一个搞砸你的设计的糟糕理由。 如果您需要更高的性能,请使用更快的服务器。

The only thing I can think of is that virtual methods are a little bit slower to call than non-virtual methods, because the call has to go through the virtual method table.

However, this is a bad reason to screw up your design. If you need more performance, use a faster server.

从﹋此江山别 2024-07-12 00:09:00

对于任何包含虚函数的类,都会使用虚函数表。 显然,通过 vtable 等调度机制调用方法比直接调用慢,但在大多数情况下您可以接受。

As for any class that contains a virtual function, a vtable is used. Obviously, invoking a method through a dispatching mechanism like a vtable is slower than a direct call, but in most cases you can live with that.

一念一轮回 2024-07-12 00:09:00

是的,但据我所知,没有什么值得注意的。 性能下降是因为每个方法调用中都有“间接”。

但是,这实际上取决于您使用的编译器,因为某些编译器无法在从抽象基类继承的类中内联方法调用。

如果您想确定,您应该运行自己的测试。

Yes, but nothing noteworthy to my knowledge. The performance hit is because of 'indirection' you have in each method call.

However, it really depends on the compiler you're using since some compilers are not able to inline the method calls within the classes inheriting from the abstract base class.

If you want to be sure you should run your own tests.

聆听风音 2024-07-12 00:09:00

是的,有处罚。 可以提高平台性能的方法是使用没有虚函数的非抽象类。 然后使用指向非虚函数的成员函数指针。

Yes, there is a penalty. Something which may improve performance on your platform is to use a non-abstract class with no virtual functions. Then use a member function pointer to your non-virtual function.

苦妄 2024-07-12 00:09:00

我知道这是一个不常见的观点,但即使提到这个问题也让我怀疑你对类结构投入了太多的思考。 我见过许多系统有太多的“抽象级别”,仅此一点就使它们容易出现严重的性能问题,不是由于方法调用的成本,而是由于倾向于进行不必要的调用。 如果这种情况发生在多个级别上,那就是致命的。 看一下

I know it's an uncommon viewpoint, but even mentioning this issue makes me suspect you're putting way too much thought into the class structure. I've seen many systems that had way too many "levels of abstraction", and that alone made them prone to severe performance problems, not due the cost of method calls, but due to the tendency to make unnecessary calls. If this happens over multiple levels, it's a killer. take a look

指尖上的星空 2024-07-12 00:09:00

大多数人都会注意到运行时间的损失,这是正确的。

然而,根据我从事大型项目的经验,清晰的接口和适当的封装所带来的好处很快就会抵消速度的提升。 模块化代码可以替换为改进的实现,因此最终结果是巨大的收益。

您的情况可能会有所不同,这显然取决于您正在开发的应用程序。

Most people note the runtime penalty, and rightly so.

However, in my experience working on large projects, the benefits from clear interfaces and proper encapsulation quickly offset the gain in speed. Modular code can be swapped for an improved implementation, so the net result is a large gain.

Your mileage may vary, and it clearly depend on the application you're developing.

海的爱人是光 2024-07-12 00:09:00

请注意,多重继承会使带有多个 vtable 指针的对象实例变得臃肿。 对于 x86 上的 G++,如果您的类有一个虚方法并且没有基类,那么您就有一个指向 vtable 的指针。 如果你有一个带有虚方法的基类,你仍然有一个指向 vtable 的指针。 如果您有两个带有虚拟方法的基类,则每个实例上都有两个 vtable 指针。

因此,通过多重继承(这就是在 C++ 中实现接口的方式),您需要在对象实例大小中支付基类乘以指针大小。 内存占用的增加可能会间接影响性能。

Note that multiple inheritance bloats the object instance with multiple vtable pointers. With G++ on x86, if your class has a virtual method and no base class, you have one pointer to vtable. If you have one base class with virtual methods, you still have one pointer to vtable. If you have two base classes with virtual methods, you have two vtable pointers on each instance.

Thus, with multiple inheritance (which is what implementing interfaces in C++ is), you pay base classes times pointer size in the object instance size. The increase in memory footprint may have indirect performance implications.

贪了杯 2024-07-12 00:09:00

与常规调用相比,每次虚拟函数调用都会受到较小的惩罚。 除非每秒执行数十万次调用,否则您不太可能观察到差异,而且无论如何,为了提高代码清晰度而付出的代价通常是值得的。

There is a small penalty per virtual function call compared to a regular call. You are unlikely to observe a difference unless you are doing hundreds of thousands of calls per second, and the price is often worth paying for added code clarity anyway.

一杆小烟枪 2024-07-12 00:09:00

当您调用虚拟函数(例如通过接口)时,程序必须在表中查找该函数,以了解要为该对象调用哪个函数。 与直接调用该函数相比,这会带来较小的损失。

此外,当您使用虚拟函数时,编译器无法内联函数调用。 因此,对某些小函数使用虚函数可能会产生惩罚。 这通常是您可能会看到的最大的性能“打击”。 如果函数很小并且被多次调用(例如在循环内),这实际上只是一个问题。

When you call a virtual function (say through an interface) the program has to do a look up of the function in a table to see which function to call for that object. This gives a small penalty compared to a direct call to the function.

Also, when you use a virtual function the compiler cannot inline the function call. Therefore there could be a penalty to using a virtual function for some small functions. This is generally the biggest performance "hit" you are likely to see. This really only an issue if the function is small and called many times, say from within a loop.

回眸一遍 2024-07-12 00:09:00

在某些情况下适用的另一种选择是编译时
与模板的多态性。 例如,当您想要时,它很有用
在计划开始时做出实施选择,以及
然后在执行期间使用它。 一个例子
运行时多态性

class AbstractAlgo
{
    virtual int func();
};

class Algo1 : public AbstractAlgo
{
    virtual int func();
};

class Algo2 : public AbstractAlgo
{
    virtual int func();
};

void compute(AbstractAlgo* algo)
{
      // Use algo many times, paying virtual function cost each time

}   

int main()
{
    int which;
     AbstractAlgo* algo;

    // read which from config file
    if (which == 1)
       algo = new Algo1();
    else
       algo = new Algo2();
    compute(algo);
}

与使用编译时多态性相同

class Algo1
{
    int func();
};

class Algo2
{
    int func();
};


template<class ALGO>  void compute()
{
    ALGO algo;
      // Use algo many times.  No virtual function cost, and func() may be inlined.
}   

int main()
{
    int which;
    // read which from config file
    if (which == 1)
       compute<Algo1>();
    else
       compute<Algo2>();
}

Another alternative that is applicable in some cases is compile-time
polymorphism with templates. It is useful, for example, when you want
to make an implementation choice at the beginning of the program, and
then use it for the duration of the execution. An example with
runtime polymorphism

class AbstractAlgo
{
    virtual int func();
};

class Algo1 : public AbstractAlgo
{
    virtual int func();
};

class Algo2 : public AbstractAlgo
{
    virtual int func();
};

void compute(AbstractAlgo* algo)
{
      // Use algo many times, paying virtual function cost each time

}   

int main()
{
    int which;
     AbstractAlgo* algo;

    // read which from config file
    if (which == 1)
       algo = new Algo1();
    else
       algo = new Algo2();
    compute(algo);
}

The same using compile time polymorphism

class Algo1
{
    int func();
};

class Algo2
{
    int func();
};


template<class ALGO>  void compute()
{
    ALGO algo;
      // Use algo many times.  No virtual function cost, and func() may be inlined.
}   

int main()
{
    int which;
    // read which from config file
    if (which == 1)
       compute<Algo1>();
    else
       compute<Algo2>();
}
慢慢从新开始 2024-07-12 00:09:00

我不认为成本比较是在虚函数调用和直接函数调用之间进行的。 如果您正在考虑使用抽象基类(接口),那么您会遇到这样的情况:您想要根据对象的动态类型执行多个操作之一。 你必须以某种方式做出这样的选择。 一种选择是使用虚函数。 另一种是通过 RTTI(可能昂贵)或向基类添加 type() 方法(可能增加每个对象的内存使用量)来切换对象的类型。 因此,虚函数调用的成本应该与替代方案的成本进行比较,而不是与不执行任何操作的成本进行比较。

I don't think that the cost comparison is between virtual function call and a straight function call. If you are thinking about using a abstract base class (interface), then you have a situation where you want to perform one of several actions based of the dynamic type of an object. You have to make that choice somehow. One option is to use virtual functions. Another is a switch on the type of the object, either through RTTI (potentially expensive), or adding a type() method to the base class (potentially increasing memory use of each object). So the cost of the virtual function call should be compared to the cost of the alternative, not to the cost of doing nothing.

爺獨霸怡葒院 2024-07-12 00:09:00

使用虚拟分派调用的函数不是内联的

虚拟函数有一种很容易被忘记的惩罚:在编译时不知道对象类型的(常见)情况下,虚拟调用不是内联的。 如果您的函数很小并且适合内联,则这种惩罚可能非常显着,因为您不仅增加了调用开销,而且编译器在优化调用函数方面也受到限制(它必须假设虚函数可能会更改了某些寄存器或内存位置,它无法在调用者和被调用者之间传播常量值)。

虚拟调用成本取决于平台

至于与普通函数调用相比的调用开销损失,答案取决于您的目标平台。 如果您的目标是具有 x86/x64 CPU 的 PC,则调用虚拟函数的损失非常小,因为现代 x86/x64 CPU 可以对间接调用执行分支预测。 但是,如果您的目标是 PowerPC 或其他一些 RISC 平台,则虚拟调用损失可能会相当大,因为在某些平台上永远不会预测间接调用(参见 PC/Xbox 360 跨平台开发最佳实践)。

Functions called using virtual dispatch are not inlined

There is one kind of penalty for virtual functions which is easy to forget about: virtual calls are not inlined in a (common) situation where the type of the object is not know compile time. If your function is small and suitable for inlining, this penalty may be very significant, as you are not only adding a call overhead, but the compiler is also limited in how it can optimize the calling function (it has to assume the virtual function may have changed some registers or memory locations, it cannot propagate constant values between the caller and the callee).

Virtual call cost depends on platform

As for the call overhead penalty compared to a normal function call, the answer depends on your target platform. If your are targeting a PC with x86/x64 CPU, the penalty for calling a virtual function is very small, as modern x86/x64 CPU can perform branch prediction on indirect calls. However, if you are targeting a PowerPC or some other RISC platform, the virtual call penalty may be quite significant, because indirect calls are never predicted on some platforms (Cf. PC/Xbox 360 Cross Platform Development Best Practices).

绮筵 2024-07-12 00:09:00

简短回答: 否。

长回答:
影响速度的并不是基类或类在其层次结构中的祖先数量。 唯一的问题是方法调用的成本。

非虚拟方法调用有成本(但可以内联)
虚拟方法调用的成本稍高,因为您需要在调用之前查找要调用的方法(但这只是简单的表查找而不是搜索)。 由于接口上的所有方法根据定义都是虚拟的,因此存在此成本。

除非您正在编写一些超速度敏感的应用程序,否则这应该不是问题。 使用界面所获得的额外清晰度通常可以弥补任何感知到的速度下降。

Short Answer: No.

Long Answer:
It is not the base class or the number of ancestors a class has in its hierarchy that affects it speed. The only thing is the cost of a method call.

A non virtual method call has a cost (but can be inlined)
A virtual method call has a slightly higher cost as you need to look up the method to call before you call it (but this is a simple table look up not a search). Since all methods on an interface are virtual by definition there is this cost.

Unless you are writing some hyper speed sensitive application this should not be a problem. The extra clarity that you will recieve from using an interface usually makes up for any perceived speed decrease.

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