我有一个实现界面的项目列表。对于这个问题,让我们使用这个示例界面:
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
具有其他具有代码的方法 - 它不仅仅是一个不执行任何操作的对象。
答案 0 :(得分:9)
这会对性能产生影响吗?
极不可能产生有意义的性能影响。
如果是这样,多少钱?
您可以使用分析器为您的特定代码路径量化完全。我们不能,因为我们不知道代码路径。我们可以猜测,并告诉你它几乎肯定无关紧要,因为这不太可能是你的应用程序的瓶颈(你真的坐在那里紧急循环调用Person.AgeAYear
吗?)。
只有您可以通过取出分析器并进行测量来找出精确。
对于所有意图和目的,空函数是否会被优化出来?
这当然有可能,但可能不会;它甚至可能在未来版本的JITter中发生变化,或者从平台变为平台(不同的平台有不同的JITter)。如果您真的想知道,请编译您的应用程序,并查看反汇编的JITted代码(而不是IL!)。
但我会这样说:这几乎肯定是,几乎绝对不值得担心或投入任何时间。除非您在性能关键代码的紧密循环中调用Person.AgeAYear
,否则它不是您应用程序中的瓶颈。你可以花时间在这上面,或者你可以花时间改进你的应用程序。你的时间也有成本。
答案 1 :(得分:3)
是可能,如果调用该函数,则调用本身将花费少量时间。
你永远不会注意到任何实际应用程序的不同之处 - 与执行任何“真实”工作的成本相比,调用方法的成本非常小。
我对此表示怀疑 - 如果方法在不同的程序集中,CLR 肯定可能不会执行这种优化,因为该方法将来可能会发生变化。 可能可行,这种优化是为程序集内的方法调用完成的,但它在很大程度上取决于代码,例如在下面的示例中:
foreach (IPerson person in people)
{
person.AgeAYear();
}
方法调用无法优化,因为可能会提供IPerson
的不同实现,这实际上在此方法中有所作为。对于IPerson
接口的任何调用肯定会出现这种情况,编译器无法证明它始终使用ImmortalPerson
实例。
最终你必须问自己“有什么选择?”并且“这确实有足够大的影响来保证替代方法吗?” 。在这种情况下,影响将非常小 - 我会说在这种情况下以这种方式使用空方法是完全可以接受的。
答案 2 :(得分:3)
你的逻辑似乎对我来说是错误的,无论性能如何影响,调用一个空方法都会闻到糟糕的设计。
在您的情况下,您有一个Of Person
的界面。您声明,为了成为一个人,您必须能够按照AgeAYear
方法强制执行年龄。但是,根据AgeAYear方法(或缺少)的逻辑,ImmortalPerson
不能老化,但仍然可以是Person
。你的逻辑与自己相矛盾。你可以通过多种方式解决这个问题,但这是第一个突然爆发的问题。
实现此目的的一种方法是设置两个接口:
interface IPerson { void Walk(); }
interface IAgeable : IPerson { void AgeAYear();}
您现在可以清楚地区分您不必成年为一个人,但为了年龄,您必须是一个人。 e.g。
class ImmortalPerson : IPerson
{
public void Walk()
{
// Do Something
}
}
class RegularPerson : IAgeable
{
public void AgeAYear()
{
// Age A Year
}
public void Walk()
{
// Walk
}
}
因此,对于RegularPerson
,通过实施IsAgeable
,您还需要实施IPerson
。对于ImmortalPerson
,您只需要实施IPerson
。
然后,您可以执行以下操作或其变体:
List<IPerson> people = new List<IPerson>();
people.Add(new ImmortalPerson());
people.Add(new RegularPerson());
foreach (var person in people)
{
if (person is IAgeable)
{
((IAgeable)person).AgeAYear();
}
}
根据上述设置,您仍然强制要求您的班级必须实施IPerson
才能被视为某个人,但只有在他们同时实施IAgeable
时才能过时。
答案 3 :(得分:1)
编译器无法理解将要调用哪两个函数,因为这是在运行时设置的函数指针。
你可以通过检查Person中的一些变量,确定它的类型或使用dynamic_cast来检查它来避免它。如果函数不需要调用,那么你可以忽略它。
调用函数包含一些指令:
当功能结束时:
它可能看起来很多,但也许它只是检查变量类型和避免调用的成本的两倍或三倍(在另一种情况下,你检查一些变量和可能跳转,几乎与调用空函数的时间相同。你只会保存返回成本。但是,你要检查需要调用的函数,所以最后你可能没有保存任何东西! )
在我看来,与单纯的函数调用相比,您的算法对代码性能的影响要大得多。所以,不要用这样的小事来欺骗自己。
大量调用空函数(可能是数百万)可能会对程序的性能产生一些影响,但如果发生这种情况,则意味着你在做一些算法错误的事情(例如,认为你应该放置NormalPersons和ImmortalPersons在同一个名单中)
答案 4 :(得分:1)
在相对较新的工作站上,C#委托或接口调用需要 2纳秒。为了比较:
DateTime.Now
(系统调用):750纳秒因此,除非您正在优化紧密循环,否则方法调用可能不会成为瓶颈。如果要优化紧密循环,请考虑更好的算法,例如在处理它们之前在Dictionary
中对事物建立索引。
我在3.75 GHz的Core i7 3770上测试了这些,使用LINQPad 32位并打开了优化。但由于内联,优化,寄存器分配和其他编译器/ JIT行为,方法调用的时间将根据上下文而有很大差异。 2纳秒只是一个大概的数字。
在您的情况下,您正在遍历列表。由于looping internally involves lots of method calls,循环开销很可能主导方法调用开销。在您的情况下,性能不太可能是一个问题,但如果您有数百万个项目和/或您经常需要更新它们,请考虑更改您表示数据的方式。例如,您可以使用单个“年”变量来全局递增,而不是增加每个人的“年龄”。