Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 将方法/属性标记为虚拟的性能影响是什么?_C#_Performance_Virtual - Fatal编程技术网

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

C# 将方法/属性标记为虚拟的性能影响是什么?,c#,performance,virtual,C#,Performance,Virtual,问题如标题所述:将方法/属性标记为虚拟的性能影响是什么 注意-我假设虚拟方法在普通情况下不会重载;我通常在这里使用基类。从您的标记中,您说的是c。我只能从德尔菲的角度来回答。我想也会是这样。(我在这里期待负面反馈:)) 静态方法将在编译时链接。虚拟方法需要在运行时进行查找以决定调用哪个方法,因此开销很小。只有当方法很小且经常调用时,它才有意义。与直接调用相比,虚拟函数的性能开销非常小。在较低的级别上,基本上是通过数组查找来获取函数指针,然后通过函数指针进行调用。现代CPU甚至可以在其分支预测器中

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


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

从您的标记中,您说的是c。我只能从德尔菲的角度来回答。我想也会是这样。(我在这里期待负面反馈:))


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

与直接调用相比,虚拟函数的性能开销非常小。在较低的级别上,基本上是通过数组查找来获取函数指针,然后通过函数指针进行调用。现代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
与直接函数调用的以下内容相比:

CALL I  ;  Call function directly
真正的开销在于虚拟函数在很大程度上不能内联。(如果虚拟机意识到它们总是指向同一个地址,那么它们可以使用JIT语言。)除了内联本身带来的加速外,内联还支持其他一些优化,例如常量折叠,因为调用方可以知道被调用方在内部是如何工作的。对于大到无论如何都不能内联的函数,性能影响可能可以忽略不计。对于可能是内联的非常小的函数,这时您需要注意虚拟函数


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

在桌面端,不管方法是否重载,它们都会通过方法指针表(虚拟方法表)产生额外级别的间接寻址,这意味着在方法调用之前,大约有2个额外的内存通过间接读取来比较非密封类和非final方法上的非虚方法

[一个有趣的事实是,在compact framework 1.0版上,开销更大,因为它不使用虚拟方法表,而只是通过反射来发现调用虚拟方法时要执行的正确方法。]

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

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

非虚拟方法<虚拟方法<接口方法(在类上)<委托分派
但是,除非您通过测量来证明,否则各种调度机制的这些性能影响都无关紧要(即使这样,架构含义、可读性等也可能对选择方法有很大的影响)

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

虽然成本并非绝对为零,但却是极其微小的。 如果它有助于你的程序拥有虚拟功能,那么一定要这样做


拥有一个设计良好、性能影响非常小的程序要比一个笨拙的程序好得多,只是为了避免v表。

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

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

也就是说,当调用非虚拟方法时,必须

  • 保存寄存器,生成函数prologue/epilogue来设置参数,复制返回值等等
  • 跳转到固定且静态已知的地址
  • 1在两种情况下都是相同的。对于2,使用虚拟方法,您必须从对象vtable中的固定偏移量读取,然后跳到该偏移量指向的任何位置。这使得分支预测更加困难,并且可能会将一些数据从CPU缓存中推出。所以差别不是很大,但是如果你把每个函数调用都虚拟化的话,它可以加起来

    它还可以抑制优化。编译器可以很容易地内联对非虚拟函数的调用,因为它确切地知道调用哪个函数。使用虚拟函数,这有点棘手。一旦确定调用哪个函数,JIT编译器可能仍然能够做到这一点,但这需要做更多的工作

    总而言之,它仍然可以累积,特别是在性能关键领域。但是,除非函数每秒至少调用几十万次,否则您不必担心这个问题。

    。虚拟函数调用(在3ghz PowerPC上)比直接函数调用长7-20纳秒。这意味着它实际上只对那些计划每秒调用一百万次的函数,或者那些开销可能大于函数本身的小函数才有意义。(例如,出于盲目的习惯而将访问器函数虚拟化可能是不明智的。)

    我还没有在C#中运行测试,但我希望差异会更小,因为CLR中几乎每个操作都涉及间接测试。