简体   繁体   中英

Performance penalty for accessing properties in base class (C#)

I was profiling my class library and optimizing things, when I've noticed this strange issue:

There's a base class, and I have other classes, derived from it. Base class has a public property. I'm doing Linq queries that involve this property elsewhere in the code.

Now, doing 100,000 iterations (not even a million) I can see that if I also add a property with the same name to the derived class (intellisense highlights it with a tooltip "This property hides inherited member"), thus making basically a 'Shortcut' (but also a duplication of the property) - Code runs significantly faster... to me 350 ms. over an iteration of 100,000 is quite significant.

Why is it, please ? :) What can be done ?


More details:

Base class:

public abstract class ContentItem: IContent
{
    internal ContentItem() { }

    [DataMember]
    [IndexedField(true, false)]
    public string Guid { get; set; }

    [DataMember]
    [IndexedField(false, true)]
    public string Title { get; set; }
}

Derived "POCO":

[IndexedClass]
public class Channel : ContentItem, IContent
{
    [DataMember(IsRequired = false, EmitDefaultValue = false)]
    [ContentField]
    public string TitleShort { get; set; }
}

Repository class (doing the linq queries): (Generic repository)

public virtual T ByTitle(string title)
{
    return All.Find(item => item.Title == title);
}

Where All is a List<Channel> and has 2700 items.

Code for testing:

private static void test(Content.Repository<Content.Channel> channels)
        {
            int iterations = 100000;

            var watch = System.Diagnostics.Stopwatch.StartNew();

            for (int i = 0; i < iterations; i++)
            {
                var channel = channels.ByTitle("Unique-Title");
            }

            watch.Stop();

            Console.WriteLine("Done in {0} ms.", watch.ElapsedMilliseconds);
        }

When you're hiding the property you're making it a non-virtual call, rather than a virtual call to a member. Virtual dispatch does have a certain cost associated with it, which is why you're able to declare non-virtual properties/methods.

That said, in most applications the costs associated with a virtual method/property are not a problem at all. There is a difference, yes, but it's not much at all in the context of most programs.

If you examine the generated IL, when your code is using the derived class' local property, it's generating a call rather than a callvirt , which is just plain cheaper.

This seems like a premature optimization unless you're in a time critical loop.

Worrying about the difference between call and callvirt performance when building iteration using linq seems.... particularly premature.

The difference in call and callvirt is really small.

I simplified your code. Please run it and tell us what answer do you have. I have no difference in using both properties.

public abstract class ContentItem 
{
    public string Title { get; set; }
    public string AnotherTitle { get; set; }
}

public class Channel : ContentItem
{
    public string AnotherTitle { get; set; }
}

private static void Main(string[] args)
{
    var channels = new List<Channel>();
    for (int i = 0; i < 3000; i++)
    {
        channels.Add(new Channel(){Title = i.ToString(), AnotherTitle = i.ToString()});
    }
    int iterations = 100000;
    System.Diagnostics.Stopwatch watch;
    var difs = new List<int>();
    int rounds = 10;
    for (int k = 0; k < rounds ; k++)
    {
        watch = System.Diagnostics.Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            var channel = channels.Find(item => item.Title == "2345");
        }
        watch.Stop();
        long timerValue = watch.ElapsedMilliseconds;
        watch = System.Diagnostics.Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            var channel = channels.Find(item => item.AnotherTitle == "2345");
        }
        watch.Stop();
        difs.Add((int)(timerValue - watch.ElapsedMilliseconds));
    }

    Console.WriteLine("result middle dif " + difs.Sum()/rounds);
}

Update

Also in this case you do not have any call methods in IL. both Title and AnotherTitle look like

IL_0008:  callvirt   instance string ConsoleApplication4.Program/ContentItem::get_Title()
IL_0016:  callvirt   instance string ConsoleApplication4.Program/Channel::get_AnotherTitle()

The problem you have has nothing to do with call or callvirt . Probably the difference is in code, that you don't show to us.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM