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.
Will this have a performance impact?
Highly unlikely to have a meaningful performance impact.
If so, how much?
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.
Will the empty function, for all intents and purposes, be optimized out?
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.
- Will this have a performance impact?
Yes Probably, if the function is called then the call itself will take a small amount of time.
- If so, by how much?
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.
- Will the empty function, for all intents and purposes, be optimized out?
I doubt it - the CLR definitely probably 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:
foreach (IPerson person in people)
{
person.AgeAYear();
}
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 the IPerson
interface where the compiler can't prove that its always working with a ImmortalPerson
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.
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 your AgeAYear
method. However, according to the logic in your AgeAYear method (or lack thereof), an ImmortalPerson
cannot age, but can still be a Person
. 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:
interface IPerson { void Walk(); }
interface IAgeable : IPerson { void AgeAYear();}
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.
class ImmortalPerson : IPerson
{
public void Walk()
{
// Do Something
}
}
class RegularPerson : IAgeable
{
public void AgeAYear()
{
// Age A Year
}
public void Walk()
{
// Walk
}
}
Thus, for your RegularPerson
, by implementing IsAgeable
, you also are required to implement IPerson
. For your ImmortalPerson
, you only need to implement IPerson
.
Then, you could do something like below, or a variation thereof:
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();
}
}
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 implement IAgeable
.
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:
- pushing the arguments on the process stack (none in this case)
- pushing return addresses and a few other data
- jumping to the function
And when the function ends:
- jumping back from the function
- changing stack pointer to effectively remove stack frame of the called function
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)
On a relatively modern workstation, a C# delegate or interface call takes around 2 nanoseconds. For comparison:
- Allocating a small array: 10 nanoseconds
- Allocating a closure: 15 nanoseconds
- Taking an uncontested lock: 25 nanoseconds
- Dictionary lookup (100 short string keys): 35 nanoseconds
DateTime.Now
(system call): 750 nanoseconds- Database call over wired network (count on a small table): 1,000,000 nanoseconds (1 millisecond)
So 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".
精彩评论