繁体   English   中英

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

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

当我注意到这个奇怪的问题时,我正在对类库进行性能分析并进行优化:

有一个基类,还有其他一些从它派生的类。 基类具有公共财产。 我正在做涉及此属性在代码中其他地方的Linq查询。

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

为什么要这样? :)可以做什么?


更多细节:

基类:

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; }
}

派生“ POCO”:

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

存储库类(执行linq查询):(通用存储库)

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

其中All是一个List<Channel> ,具有2700个项目。

测试代码:

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);
        }

隐藏属性时,是在进行非虚拟调用,而不是对成员的虚拟调用。 虚拟调度确实有一定的成本,这就是为什么您能够声明非虚拟属性/方法的原因。

也就是说,在大多数应用程序中,与虚拟方法/属性相关联的成本根本不是问题。 是的,是有区别的,但是在大多数程序的上下文中并没有多大区别。

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

除非您处于时间紧迫的循环,否则这似乎是过早的优化。

使用linq构建迭代时,担心callcallvirt性能之间的差异。

call和callvirt的区别确实很小。

我简化了您的代码。 请运行它,并告诉我们您的答案是什么。 使用这两个属性没有区别。

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);
}

更新资料

同样在这种情况下,IL中没有任何call方法。 TitleAnotherTitle看起来都像

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

您遇到的问题与callcallvirt 您可能不向我们展示,可能是代码不同。

暂无
暂无

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

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