将方法/属性标记为虚拟的性能影响是什么?

时间:2022-03-30 16:54:25

Question is as stated in the title: What are the performance implications of marking methods / properties as virtual?

问题如标题所述:将方法/属性标记为虚拟的性能影响是什么?

Note - I'm assuming the virtual methods will not be overloaded in the common case; I'll usually be working with the base class here.

注意 - 我假设虚拟方法在常见情况下不会过载;我通常会在这里使用基类。

7 个解决方案

#1


Virtual functions only have a very small performance overhead compared to direct calls. At a low level, you're basically looking at an array lookup to get a function pointer, and then a call via a function pointer. Modern CPUs can even predict indirect function calls reasonably well in their branch predictors, so they generally won't hurt modern CPU pipelines too badly. At the assembly level, a virtual function call translates to something like the following, where I is an arbitrary immediate value.

与直接调用相比,虚拟功能仅具有非常小的性能开销。在较低级别,您基本上是在查找数组查找以获取函数指针,然后通过函数指针进行调用。现代CPU甚至可以在其分支预测器中合理地预测间接函数调用,因此它们通常不会太严重地损害现代CPU管道。在汇编级别,虚函数调用转换为类似下面的内容,其中I是任意立即值。

MOV EAX, [EBP + I] ; Move pointer to class instance into register
MOV EBX, [EAX] ;  Move vtbl pointer into register.
CALL [EBX + I]  ;   Call function

Vs. the following for a direct function call:

比。以下为直接函数调用:

CALL I  ;  Call function directly

The real overhead comes in that virtual functions can't be inlined, for the most part. (They can be in JIT languages if the VM realizes they're always going to the same address anyhow.) Besides the speedup you get from inlining itself, inlining enables several other optimizations such as constant folding, because the caller can know how the callee works internally. For functions that are large enough not to be inlined anyhow, the performance hit will likely be negligible. For very small functions that might be inlined, that's when you need to be careful about virtual functions.

实际的开销是因为大多数情况下虚拟函数无法内联。 (他们可以在JIT语言如果VM认识他们总是要相同的地址无论如何)。除此之外,您还从内联本身得到加速,内联使几个其他优化,如常量折叠,因为主叫方可以知道如何被调用者内部工作。对于任何大到不能内联的函数,性能损失可能是微不足道的。对于可能内联的非常小的函数,当您需要注意虚函数时。

Edit: Another thing to keep in mind is that all programs require flow control, and this is never free. What would replace your virtual function? A switch statement? A series of if statements? These are still branches that may be unpredictable. Furthermore, given an N-way branch, a series of if statements will find the proper path in O(N), while a virtual function will find it in O(1). The switch statement may be O(N) or O(1) depending on whether it is optimized to a jump table.

编辑:要记住的另一件事是所有程序都需要流量控制,这绝不是免费的。什么会取代你的虚拟功能?一个开关声明?一系列if语句?这些仍然是可能无法预测的分支。此外,给定N路分支,一系列if语句将在O(N)中找到正确的路径,而虚函数将在O(1)中找到它。 switch语句可以是O(N)或O(1),这取决于它是否针对跳转表进行了优化。

#2


Rico Mariani outlines issues regarding performance in his Performance Tidbits blog, where he stated:

Rico Mariani在他的Performance Tidbits博客中概述了有关性能的问题,他说:

Virtual Methods: Are you using virtual methods when direct calls would do? Many times people go with virtual methods to allow for future extensibility. Extensibility is a good thing but it does come at a price – make sure your full extensibility story is worked out and that your use of virtual functions is actually going to get you to where you need to be. For instance, sometimes people think through the call site issues but then don’t consider how the “extended” objects are going to be created. Later they realize that (most of) the virtual functions didn’t help at all and they needed an entirely different model to get the “extended” objects into the system.

虚拟方法:直接调用时,您使用的是虚拟方法吗?很多时候人们使用虚拟方法来实现未来的可扩展性。可扩展性是一件好事,但它确实付出了代价 - 确保您的完整可扩展性故事得到解决,并且您使用虚拟功能实际上将使您到达您需要的位置。例如,有时人们会通过调用站点问题进行思考,但之后不会考虑如何创建“扩展”对象。后来他们意识到(大部分)虚拟功能根本没有帮助,他们需要一个完全不同的模型来将“扩展”对象引入系统。

Sealing: Sealing can be a way of limiting the polymorphism of your class to just those sites where polymorphism is needed. If you will fully control the type then sealing can be a great thing for performance as it enables direct calls and inlining.

密封:密封可以是一种将类的多态性限制为仅需要多态性的站点的方法。如果您将完全控制类型,那么密封对于性能来说是一件好事,因为它可以实现直接调用和内联。

Basically the argument against virtual methods is that it disallows the code to be a candidate of in-lining, as opposed to direct calls.

基本上反对虚方法的论点是它不允许代码成为内联的候选者,而不是直接调用。

In the MSDN article Improving .NET Application Performance and Scalability, this is further expounded:

在MSDN文章“提高.NET应用程序性能和可伸缩性”中,进一步阐述了这一点:

Consider the Tradeoffs of Virtual Members

考虑虚拟会员的权衡

Use virtual members to provide extensibility. If you do not need to extend your class design, avoid virtual members because they are more expensive to call due to a virtual table lookup and they defeat certain run-time performance optimizations. For example, virtual members cannot be inlined by the compiler. Additionally, when you allow subtyping, you actually present a very complex contract to consumers and you inevitably end up with versioning problems when you attempt to upgrade your class in the future.

使用虚拟成员提供可扩展性。如果您不需要扩展类设计,请避免使用虚拟成员,因为由于虚拟表查找而导致调用成本更高,并且它们会使某些运行时性能优化失败。例如,编译器无法内联虚拟成员。此外,当您允许子类型化时,您实际上向消费者提供了一份非常复杂的合同,并且当您将来尝试升级您的课程时,您不可避免地会遇到版本问题。

A criticism of the above, however, comes from the TDD/BDD camp (who wants methods defaulting to virtual) arguing that the performance impact is negligible anyway, especially as we get access to much faster machines.

然而,对上述内容的批评来自TDD / BDD阵营(他们希望方法默认为虚拟),认为性能影响无论如何都可以忽略不计,特别是当我们可以访问速度更快的机器时。

#3


Typically a virtual method simply goes through one table-of-function-pointers to reach the actual method. This means one extra dereference and one more round-trip to memory.

通常,虚拟方法只需通过一个函数表指针即可到达实际方法。这意味着一个额外的解引用和一个往返内存。

While the cost is not absolutely ZERO, it is extremely minimal. If it helps your program at all to have virtual functions, by all means, do it.

虽然成本并非绝对零,但它非常小。如果它可以帮助你的程序拥有虚拟功能,那么一定要做到这一点。

Its far better to have a well-designed program with a tiny, tiny, tiny performance hit rather than a clumsy program just for the sake of avoiding the v-table.

为了避免使用v-table,它有一个精心设计的程序,具有微小的,微小的性能命中,而不是一个笨拙的程序。

#4


It's hard to say for sure, because the .NET JIT compiler may be able to optimize the overhead away in some (many?) cases.

很难说,因为.NET JIT编译器可能能够在一些(很多?)情况下优化开销。

But if it does not optimize it away, we are basically talking about an extra pointer indirection.

但如果它没有优化它,我们基本上是在谈论一个额外的指针间接。

That is, when you call a non-virtual method, you have to

也就是说,当你调用非虚方法时,你必须这样做

  1. Save registers, generate the function prologue/epilogue to set up arguments, copy the return value and such.
  2. 保存寄存器,生成函数prologue / epilogue以设置参数,复制返回值等。

  3. jump to a fixed, and statically known, address
  4. 跳转到一个固定的,静态知道的地址

1 is the same in both cases. As for 2, with a virtual method, you have to instead read from a fixed offset in the object's vtable, and then jump to wherever that points. That makes branch prediction harder, and it may push some data out of the CPU cache. So the difference isn't huge, but it can add up if you make every function call virtual.

在两种情况下,1都是相同的。对于2,使用虚方法,您必须从对象的vtable中的固定偏移读取,然后跳转到该点的任何位置。这使分支预测更难,并且可能会将一些数据从CPU缓存中推出。所以区别并不大,但是如果你将每个函数调用为虚拟,它可以加起来。

It can also inhibit optimizations. The compiler can easily inline a call to a nonvirtual function, because it knows exactly which function is called. With a virtual function, that is a bit trickier. The JIT-compiler may still be able to do it, once it's determined which function is called, but it's a lot more work.

它还可以抑制优化。编译器可以轻松地内联对非虚函数的调用,因为它确切地知道调用了哪个函数。使用虚函数,这有点棘手。一旦确定调用了哪个函数,JIT编译器仍然可以执行它,但是它的工作量要大得多。

All in all, it can still add up, especially in performance-critical areas. But it's not something you need to worry about unless the function is called at the very least a few hundred thousand times per second.

总而言之,它仍然可以加起来,特别是在性能关键领域。但这不是你需要担心的事情,除非每秒至少调用几十万次函数。

#5


I ran this test in C++. A virtual function call takes (on a 3ghz PowerPC) between 7-20 nanoseconds longer than a direct function call. That means it really only matters for functions you plan on calling a million times per second, or for functions that are so small that the overhead may be larger than the function itself. (For example, making accessor functions virtual out of blind habit is probably unwise.)

我用C ++运行了这个测试。虚拟函数调用(在3ghz PowerPC上)比直接函数调用长7-20纳秒。这意味着它实际上只对你计划每秒调用一百万次的函数或者对于那么小的函数来说很重要,以至于开销可能比函数本身大。 (例如,将访问者功能虚拟化为盲目习惯可能是不明智的。)

I haven't run my test in C#, but I expect that the difference will be even less there, since nearly every operation in the CLR involves an indirect anyway.

我没有在C#中运行我的测试,但是我认为差异会更小,因为CLR中的几乎所有操作都涉及间接。

#6


From your tags, you're talking c#. I can only answer from a Delphi perspective. I think it will be similar. (I am expecting negative feedback here :) )

从您的标签中,您正在谈论c#。我只能从德尔福的角度回答。我认为它会是类似的。 (我期待这里的负面反馈:))

A static method will be linked at compile time. A virtual method requires a lookup at run-time to decide which method to call, so there is a small overhead. It is only significant if the method is small and called often.

静态方法将在编译时链接。虚方法需要在运行时查找以决定调用哪个方法,因此开销很小。只有当方法很小并经常调用时才有意义。

#7


On the desktop side it doesn't matter if the method are overloaded or not, they incur a extra level of indirection through the method pointer table (Virtual method table), which means roughly 2 extra memory reads through indirection before the method call compared a non virtual methods on non sealed classes and non final methods.

在桌面端,无论方法是否过载都无关紧要,它们通过方法指针表(虚方法表)产生额外的间接级别,这意味着在方法调用之前通过间接方式大约有2个额外的内存读取比较a非密封类和非最终方法的非虚方法。

[As an interesting fact, on compact framework version 1.0 the overheat is greater as it doesn't use virtual method tables but simply reflection to discover the right method to execute when calling a virtual method.]

[有趣的是,在紧凑的框架版本1.0上,过热更大,因为它不使用虚方法表,而只是反射以发现在调用虚方法时执行的正确方法。

Also virtual methods are far less likely to be candidates for inlining or other optimizations like tail call than non virtual methods.

此外,与非虚拟方法相比,虚拟方法不太可能成为内联或其他优化(如尾调用)的候选者。

Roughly this is the performance hierarchy of method calls:

大致这是方法调用的性能层次结构:

Non virtual methods < Virtual Metods < Interface methods (on classes) < Delegate dispatch < MethodInfo.Invoke < Type.InvokeMember

非虚方法

But none of these performance implications of various dispatch mechanisms don't matter unless you proven it by measuring ;) (And even then the architecture implications, readability etc might have a big weight on which one to chose)

但是,除非你通过测量证明它,否则各种调度机制的这些性能影响都无关紧要;)(即使这样,架构含义,可读性等也可能对选择哪一个有很大的影响)

#1


Virtual functions only have a very small performance overhead compared to direct calls. At a low level, you're basically looking at an array lookup to get a function pointer, and then a call via a function pointer. Modern CPUs can even predict indirect function calls reasonably well in their branch predictors, so they generally won't hurt modern CPU pipelines too badly. At the assembly level, a virtual function call translates to something like the following, where I is an arbitrary immediate value.

与直接调用相比,虚拟功能仅具有非常小的性能开销。在较低级别,您基本上是在查找数组查找以获取函数指针,然后通过函数指针进行调用。现代CPU甚至可以在其分支预测器中合理地预测间接函数调用,因此它们通常不会太严重地损害现代CPU管道。在汇编级别,虚函数调用转换为类似下面的内容,其中I是任意立即值。

MOV EAX, [EBP + I] ; Move pointer to class instance into register
MOV EBX, [EAX] ;  Move vtbl pointer into register.
CALL [EBX + I]  ;   Call function

Vs. the following for a direct function call:

比。以下为直接函数调用:

CALL I  ;  Call function directly

The real overhead comes in that virtual functions can't be inlined, for the most part. (They can be in JIT languages if the VM realizes they're always going to the same address anyhow.) Besides the speedup you get from inlining itself, inlining enables several other optimizations such as constant folding, because the caller can know how the callee works internally. For functions that are large enough not to be inlined anyhow, the performance hit will likely be negligible. For very small functions that might be inlined, that's when you need to be careful about virtual functions.

实际的开销是因为大多数情况下虚拟函数无法内联。 (他们可以在JIT语言如果VM认识他们总是要相同的地址无论如何)。除此之外,您还从内联本身得到加速,内联使几个其他优化,如常量折叠,因为主叫方可以知道如何被调用者内部工作。对于任何大到不能内联的函数,性能损失可能是微不足道的。对于可能内联的非常小的函数,当您需要注意虚函数时。

Edit: Another thing to keep in mind is that all programs require flow control, and this is never free. What would replace your virtual function? A switch statement? A series of if statements? These are still branches that may be unpredictable. Furthermore, given an N-way branch, a series of if statements will find the proper path in O(N), while a virtual function will find it in O(1). The switch statement may be O(N) or O(1) depending on whether it is optimized to a jump table.

编辑:要记住的另一件事是所有程序都需要流量控制,这绝不是免费的。什么会取代你的虚拟功能?一个开关声明?一系列if语句?这些仍然是可能无法预测的分支。此外,给定N路分支,一系列if语句将在O(N)中找到正确的路径,而虚函数将在O(1)中找到它。 switch语句可以是O(N)或O(1),这取决于它是否针对跳转表进行了优化。

#2


Rico Mariani outlines issues regarding performance in his Performance Tidbits blog, where he stated:

Rico Mariani在他的Performance Tidbits博客中概述了有关性能的问题,他说:

Virtual Methods: Are you using virtual methods when direct calls would do? Many times people go with virtual methods to allow for future extensibility. Extensibility is a good thing but it does come at a price – make sure your full extensibility story is worked out and that your use of virtual functions is actually going to get you to where you need to be. For instance, sometimes people think through the call site issues but then don’t consider how the “extended” objects are going to be created. Later they realize that (most of) the virtual functions didn’t help at all and they needed an entirely different model to get the “extended” objects into the system.

虚拟方法:直接调用时,您使用的是虚拟方法吗?很多时候人们使用虚拟方法来实现未来的可扩展性。可扩展性是一件好事,但它确实付出了代价 - 确保您的完整可扩展性故事得到解决,并且您使用虚拟功能实际上将使您到达您需要的位置。例如,有时人们会通过调用站点问题进行思考,但之后不会考虑如何创建“扩展”对象。后来他们意识到(大部分)虚拟功能根本没有帮助,他们需要一个完全不同的模型来将“扩展”对象引入系统。

Sealing: Sealing can be a way of limiting the polymorphism of your class to just those sites where polymorphism is needed. If you will fully control the type then sealing can be a great thing for performance as it enables direct calls and inlining.

密封:密封可以是一种将类的多态性限制为仅需要多态性的站点的方法。如果您将完全控制类型,那么密封对于性能来说是一件好事,因为它可以实现直接调用和内联。

Basically the argument against virtual methods is that it disallows the code to be a candidate of in-lining, as opposed to direct calls.

基本上反对虚方法的论点是它不允许代码成为内联的候选者,而不是直接调用。

In the MSDN article Improving .NET Application Performance and Scalability, this is further expounded:

在MSDN文章“提高.NET应用程序性能和可伸缩性”中,进一步阐述了这一点:

Consider the Tradeoffs of Virtual Members

考虑虚拟会员的权衡

Use virtual members to provide extensibility. If you do not need to extend your class design, avoid virtual members because they are more expensive to call due to a virtual table lookup and they defeat certain run-time performance optimizations. For example, virtual members cannot be inlined by the compiler. Additionally, when you allow subtyping, you actually present a very complex contract to consumers and you inevitably end up with versioning problems when you attempt to upgrade your class in the future.

使用虚拟成员提供可扩展性。如果您不需要扩展类设计,请避免使用虚拟成员,因为由于虚拟表查找而导致调用成本更高,并且它们会使某些运行时性能优化失败。例如,编译器无法内联虚拟成员。此外,当您允许子类型化时,您实际上向消费者提供了一份非常复杂的合同,并且当您将来尝试升级您的课程时,您不可避免地会遇到版本问题。

A criticism of the above, however, comes from the TDD/BDD camp (who wants methods defaulting to virtual) arguing that the performance impact is negligible anyway, especially as we get access to much faster machines.

然而,对上述内容的批评来自TDD / BDD阵营(他们希望方法默认为虚拟),认为性能影响无论如何都可以忽略不计,特别是当我们可以访问速度更快的机器时。

#3


Typically a virtual method simply goes through one table-of-function-pointers to reach the actual method. This means one extra dereference and one more round-trip to memory.

通常,虚拟方法只需通过一个函数表指针即可到达实际方法。这意味着一个额外的解引用和一个往返内存。

While the cost is not absolutely ZERO, it is extremely minimal. If it helps your program at all to have virtual functions, by all means, do it.

虽然成本并非绝对零,但它非常小。如果它可以帮助你的程序拥有虚拟功能,那么一定要做到这一点。

Its far better to have a well-designed program with a tiny, tiny, tiny performance hit rather than a clumsy program just for the sake of avoiding the v-table.

为了避免使用v-table,它有一个精心设计的程序,具有微小的,微小的性能命中,而不是一个笨拙的程序。

#4


It's hard to say for sure, because the .NET JIT compiler may be able to optimize the overhead away in some (many?) cases.

很难说,因为.NET JIT编译器可能能够在一些(很多?)情况下优化开销。

But if it does not optimize it away, we are basically talking about an extra pointer indirection.

但如果它没有优化它,我们基本上是在谈论一个额外的指针间接。

That is, when you call a non-virtual method, you have to

也就是说,当你调用非虚方法时,你必须这样做

  1. Save registers, generate the function prologue/epilogue to set up arguments, copy the return value and such.
  2. 保存寄存器,生成函数prologue / epilogue以设置参数,复制返回值等。

  3. jump to a fixed, and statically known, address
  4. 跳转到一个固定的,静态知道的地址

1 is the same in both cases. As for 2, with a virtual method, you have to instead read from a fixed offset in the object's vtable, and then jump to wherever that points. That makes branch prediction harder, and it may push some data out of the CPU cache. So the difference isn't huge, but it can add up if you make every function call virtual.

在两种情况下,1都是相同的。对于2,使用虚方法,您必须从对象的vtable中的固定偏移读取,然后跳转到该点的任何位置。这使分支预测更难,并且可能会将一些数据从CPU缓存中推出。所以区别并不大,但是如果你将每个函数调用为虚拟,它可以加起来。

It can also inhibit optimizations. The compiler can easily inline a call to a nonvirtual function, because it knows exactly which function is called. With a virtual function, that is a bit trickier. The JIT-compiler may still be able to do it, once it's determined which function is called, but it's a lot more work.

它还可以抑制优化。编译器可以轻松地内联对非虚函数的调用,因为它确切地知道调用了哪个函数。使用虚函数,这有点棘手。一旦确定调用了哪个函数,JIT编译器仍然可以执行它,但是它的工作量要大得多。

All in all, it can still add up, especially in performance-critical areas. But it's not something you need to worry about unless the function is called at the very least a few hundred thousand times per second.

总而言之,它仍然可以加起来,特别是在性能关键领域。但这不是你需要担心的事情,除非每秒至少调用几十万次函数。

#5


I ran this test in C++. A virtual function call takes (on a 3ghz PowerPC) between 7-20 nanoseconds longer than a direct function call. That means it really only matters for functions you plan on calling a million times per second, or for functions that are so small that the overhead may be larger than the function itself. (For example, making accessor functions virtual out of blind habit is probably unwise.)

我用C ++运行了这个测试。虚拟函数调用(在3ghz PowerPC上)比直接函数调用长7-20纳秒。这意味着它实际上只对你计划每秒调用一百万次的函数或者对于那么小的函数来说很重要,以至于开销可能比函数本身大。 (例如,将访问者功能虚拟化为盲目习惯可能是不明智的。)

I haven't run my test in C#, but I expect that the difference will be even less there, since nearly every operation in the CLR involves an indirect anyway.

我没有在C#中运行我的测试,但是我认为差异会更小,因为CLR中的几乎所有操作都涉及间接。

#6


From your tags, you're talking c#. I can only answer from a Delphi perspective. I think it will be similar. (I am expecting negative feedback here :) )

从您的标签中,您正在谈论c#。我只能从德尔福的角度回答。我认为它会是类似的。 (我期待这里的负面反馈:))

A static method will be linked at compile time. A virtual method requires a lookup at run-time to decide which method to call, so there is a small overhead. It is only significant if the method is small and called often.

静态方法将在编译时链接。虚方法需要在运行时查找以决定调用哪个方法,因此开销很小。只有当方法很小并经常调用时才有意义。

#7


On the desktop side it doesn't matter if the method are overloaded or not, they incur a extra level of indirection through the method pointer table (Virtual method table), which means roughly 2 extra memory reads through indirection before the method call compared a non virtual methods on non sealed classes and non final methods.

在桌面端,无论方法是否过载都无关紧要,它们通过方法指针表(虚方法表)产生额外的间接级别,这意味着在方法调用之前通过间接方式大约有2个额外的内存读取比较a非密封类和非最终方法的非虚方法。

[As an interesting fact, on compact framework version 1.0 the overheat is greater as it doesn't use virtual method tables but simply reflection to discover the right method to execute when calling a virtual method.]

[有趣的是,在紧凑的框架版本1.0上,过热更大,因为它不使用虚方法表,而只是反射以发现在调用虚方法时执行的正确方法。

Also virtual methods are far less likely to be candidates for inlining or other optimizations like tail call than non virtual methods.

此外,与非虚拟方法相比,虚拟方法不太可能成为内联或其他优化(如尾调用)的候选者。

Roughly this is the performance hierarchy of method calls:

大致这是方法调用的性能层次结构:

Non virtual methods < Virtual Metods < Interface methods (on classes) < Delegate dispatch < MethodInfo.Invoke < Type.InvokeMember

非虚方法

But none of these performance implications of various dispatch mechanisms don't matter unless you proven it by measuring ;) (And even then the architecture implications, readability etc might have a big weight on which one to chose)

但是,除非你通过测量证明它,否则各种调度机制的这些性能影响都无关紧要;)(即使这样,架构含义,可读性等也可能对选择哪一个有很大的影响)