调用一个空函数需要多长时间?
我有一个实现接口的项目列表。对于这个问题,让我们使用这个示例界面:
interface Person
{
void AgeAYear();
}
有两个类
class NormalPerson : Person
{
int age = 0;
void AgeAYear()
{
age++;
//do some more stuff...
}
}
class ImmortalPerson : Person
{
void AgeAYear()
{
//do nothing...
}
}
由于其他原因,我需要它们都在列表中。但是对于这个调用,当我循环遍历 Person 列表时,我可能正在调用空函数。 这会对性能产生影响吗?如果是的话,多少? 出于所有意图和目的,空函数是否会被优化掉?
注意:在真实的示例中,ImmortalPerson
还有其他确实有代码的方法 - 它不仅仅是一个不执行任何操作的对象。
I have a list of items implementing an interface. For the question, let's use this example interface:
interface Person
{
void AgeAYear();
}
There are two classes
class NormalPerson : Person
{
int age = 0;
void AgeAYear()
{
age++;
//do some more stuff...
}
}
class ImmortalPerson : Person
{
void AgeAYear()
{
//do nothing...
}
}
For other reasons, I need them both of the list. But for this call, when I loop through my list of Person
s, I may be calling empty functions. Will this have a performance impact? If so, how much? Will the empty function, for all intents and purposes, be optimized out?
NOTE: In the real example, the ImmortalPerson
has other methods that do have code - it is not just an object that does nothing.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
极不可能对性能产生有意义影响。
您可以使用探查器针对您的特定代码路径准确对其进行量化。我们不能,因为我们不知道代码路径。我们可以猜测,并告诉您这几乎肯定无关紧要,因为这极不可能成为您应用程序中的瓶颈(您真的坐在那里在紧密循环中调用
Person.AgeAYear
吗?)。只有您可以通过拿出分析器并进行测量来准确找到答案。
这当然有可能,但也可能不会;它甚至可能在 JITter 的未来版本中发生变化,或者从一个平台到另一个平台发生变化(不同的平台有不同的 JITters)。如果您确实想知道,请编译您的应用程序,然后查看反汇编的 JIT 代码(不是 IL!)。
但我要说的是:这几乎肯定、几乎绝对不值得担心或投入任何时间。除非您在性能关键代码的紧密循环中调用
Person.AgeAYear
,否则它不会成为应用程序的瓶颈。您可以花时间在这上面,或者您可以花时间改进您的应用程序。你的时间也有机会成本。Highly unlikely to have a meaningful performance impact.
You can quantify it exactly for your specific code paths using a profiler. We can not, because we don't know the code paths. We can guess, and tell you it almost surely doesn't matter because this is extremely unlikely to be a bottleneck in your application (are you really sitting there calling
Person.AgeAYear
in a tight loop?).Only you can find out precisely by getting out a profiler and measuring.
It's certainly possible but it might not; it might even change in future version of the JITter, or change from platform to platform (different platforms have different JITters). If you really want to know, compile your application, and look at the disassembled JITted code (not the IL!).
But I'll say this: this is almost surely, almost definitely not something worth worrying about or putting any time into. Unless you are calling
Person.AgeAYear
in a tight loop in performance critical code, it's not a bottleneck in your application. You could spend time on this, or you could spend time improving your application. Your time has an opportunity cost too.是如果调用该函数,则调用本身可能会花费少量时间。您永远不会注意到任何实际应用程序中的差异 - 与执行任何“实际”工作的成本相比,调用方法的成本非常小。
我对此表示怀疑 - 如果该方法位于不同的程序集中,CLR
肯定可能不会执行这种优化,因为该方法将来可能会发生变化。对于程序集中的方法调用进行这种优化可能是可行的,但这在很大程度上取决于代码,例如在下面的示例中:方法调用不能 被优化,因为可能提供不同的
IPerson
实现,它实际上在此方法中执行某些操作。对于IPerson
接口的任何调用肯定会出现这种情况,其中编译器无法证明它始终使用ImmortalPerson
实例。最终你必须问自己“还有什么选择?”以及“这真的有足够大的影响来保证采取替代方法吗?” 。在这种情况下,影响将非常小 - 我想说,在这种情况下,以这种方式使用空方法是完全可以接受的。
YesProbably, if the function is called then the call itself will take a small amount of time.You will never notice the difference in any real application - the cost of calling a method is very small compared to the cost of doing any "real" work.
I doubt it - the CLR
definitelyprobably won't perform this sort of optimisation if the method is in a different assembly as the method may change in the future. It might be feasible that this sort of optimisation is done for method calls inside the assembly but it would depend a lot on the code, for example in the following sample:The method call can't be optimised out because a different implementation of
IPerson
might be supplied which actually does something in this method. This would certainly be the case for any calls against theIPerson
interface where the compiler can't prove that its always working with aImmortalPerson
instance.Ultimately you have to ask yourself "What is the alternative?" and "Does this really have a large enough impact to warrant an alternative approach?" . In this case the impact will be very small - I would say that in this case having an empty method in this way is perfectly acceptable.
在我看来,您的逻辑似乎有问题,并且无论对性能的影响如何,调用空方法都会散发出糟糕的设计味道。
在您的情况下,您有一个接口,即
Of Person
。您声明,要成为一个人,您必须能够变老,正如您的AgeAYear
方法所强制执行的那样。但是,根据 AgeAYear 方法中的逻辑(或缺乏逻辑),ImmortalPerson
不能老化,但仍然可以是Person
。你的逻辑自相矛盾。您可以通过多种方式解决这个问题,但这是我首先想到的。实现这一目标的一种方法是设置两个界面:
你现在可以清楚地区分,你不必变老才能成为一个人,但为了变老,你必须成为一个人。例如
,对于您的
RegularPerson
,通过实现IsAgeable
,您还需要实现IPerson
。对于您的ImmortalPerson
,您只需要实现IPerson
。然后,您可以执行如下操作或其变体:
鉴于上述设置,您仍然强制您的类必须实现
IPerson
才能被视为一个人,但只有在它们也实现时才能老化IAgeable
。Your logic seems faulty to me, and regardless of the performace impact, calling an empty method smells of poor design.
In your situation, you have one interface which is
Of Person
. You are stating that in order to be a person, you must be able to age, as enforced by yourAgeAYear
method. However, according to the logic in your AgeAYear method (or lack thereof), anImmortalPerson
cannot age, but can still be aPerson
. Your logic contradicts itself. You could attack this problem a number of ways, however this is the first that pops into my head.One way to accomplish this would be to set up two interfaces:
You can now clearly distinguish that you do not have to age to be a person, but in order to age, you must be a person. e.g.
Thus, for your
RegularPerson
, by implementingIsAgeable
, you also are required to implementIPerson
. For yourImmortalPerson
, you only need to implementIPerson
.Then, you could do something like below, or a variation thereof:
Given the setup above, your still enforcing that your classes must implement
IPerson
to be considered a person, but can only be aged if they also implementIAgeable
.编译器无法了解将调用这两个函数中的哪一个,因为这是在运行时设置的函数指针。
您可以通过检查 Person 内部的某些变量、确定其类型或使用dynamic_cast 来检查它来避免它。如果该函数不需要调用,则可以忽略它。
调用函数由几条指令组成:
当函数结束时:
对您来说可能看起来很多,但也许它只是检查变量类型并避免调用的成本的两倍或三倍(在另一种情况下,您检查了一些变量和可能的跳转,这几乎与调用一个你只会节省返回成本。但是,你会检查需要调用的函数,所以最终你可能不会节省任何东西!)
在我看来,你的算法有很多。与单纯的函数调用相比,它对代码性能的影响更大。所以,不要因为这样的小事而困扰自己。
大量(也许数百万)调用空函数可能会对程序的性能产生一些影响,但如果发生这种情况,则意味着您在算法上做了一些错误的事情(例如,认为应该将 NormalPersons 和 ImmortalPersons 放在同一个中)列表)
There is no way for the compiler to understand which of the two functions is going to be called, as that is a function pointer set at run time.
You could avoid it, through checking of some variable inside Person, determining its type or use of dynamic_cast to check it. If the function didn't need calling, then you can ignore it.
Calling a function consists of a few instructions:
And when the function ends:
It may look a lot to you, but perhaps it is just maybe twice or three times the cost of checking the type of the variable and avoiding the call (in the other case, you have a check of some variable and a possible jump, which takes almost the same time as calling an empty function. You would only be saving the return cost. However, you do the check for the functions that DO need to be called, so in the end you are probably not saving anything!)
In my opinion, your algorithm has a much much greater impact on the performance of your code than a mere function call. So, don't bug yourself with small things like this.
Calling empty functions in large numbers, maybe millions, may have some effect on your program's performance, yet if such a thing happens, it means that you are doing something algorithmically wrong (for example, thinking that you should put NormalPersons and ImmortalPersons in the same list)
在相对现代的工作站上,C# 委托或接口调用大约需要2 纳秒。用于比较:
DateTime.Now
(系统调用):750纳秒所以除非你优化紧密循环,方法调用不太可能成为瓶颈。如果您正在优化紧密循环,请考虑更好的算法,例如在处理事物之前在
Dictionary
中索引事物。我在 3.40 GHz 的 Core i7 3770 上测试了这些,使用 LINQPad 32 位并启用了优化。但由于内联、优化、寄存器分配和其他编译器/JIT 行为,方法调用的时间将根据上下文而有很大差异。 2 纳秒只是一个大概数字。
就您而言,您正在循环列表。循环开销可能会主导方法调用开销,因为 内部循环涉及大量方法调用。在您的情况下,性能不太可能成为问题,但如果您有数百万个项目和/或您定期需要更新它们,请考虑更改表示数据的方式。例如,您可以有一个全局递增的“年份”变量,而不是递增每个人的“年龄”。
On a relatively modern workstation, a C# delegate or interface call takes around 2 nanoseconds. For comparison:
DateTime.Now
(system call): 750 nanosecondsSo unless you are optimizing a tight loop, method calls are not likely to be a bottleneck. If you are optimizing a tight loop, consider a better algorithm, such as indexing things in a
Dictionary
before processing them.I tested these on a Core i7 3770 at 3.40 GHz, using LINQPad 32-bit with optimization turned on. But due to inlining, optimization, register allocation, and other compiler/JIT behavior, the timing of a method call will vary wildly depending on context. 2 nanoseconds is just a ballpark figure.
In your case, you are looping through lists. The looping overhead is likely to dominate the method call overhead, since looping internally involves lots of method calls. Performance is not likely to be an issue in your case, but if you have millions of items and/or you regularly need to update them, consider changing how you represent the data. For example, you could have a single "year" variable that you increment globally instead of incrementing everyone's "age".