简体   繁体   English

在基类(C#)中访问属性的性能损失

[英]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. 我正在做涉及此属性在代码中其他地方的Linq查询。

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. 现在,进行了100,000次迭代(甚至没有一百万次迭代),我可以看到,如果我还在派生类中添加了一个具有相同名称的属性(intellisense用工具提示“此属性隐藏了继承的成员”突出显示了该属性),因此基本上使“快捷方式”(而且也是该属性的重复项)-代码运行速度显着提高...对我来说是350毫秒。 over an iteration of 100,000 is quite significant. 超过100,000的迭代非常重要。

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": 派生“ 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) 存储库类(执行linq查询):(通用存储库)

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

Where All is a List<Channel> and has 2700 items. 其中All是一个List<Channel> ,具有2700个项目。

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. 如果检查生成的IL,则当代码使用派生类的local属性时,它会生成一个call而不是callvirt ,这只是便宜得多。

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. 使用linq构建迭代时,担心callcallvirt性能之间的差异。

The difference in call and callvirt is really small. call和callvirt的区别确实很小。

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. 同样在这种情况下,IL中没有任何call方法。 both Title and AnotherTitle look like TitleAnotherTitle看起来都像

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 . 您遇到的问题与callcallvirt Probably the difference is in code, that you don't show to us. 您可能不向我们展示,可能是代码不同。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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