访问基类 (C#) 中的属性的性能损失
本文关键字:属性 性能 损失 基类 访问 | 更新日期: 2023-09-27 18:34:21
当我注意到这个奇怪的问题时,我正在分析我的类库并优化内容:
有一个基类,我还有其他类,从它派生。基类具有公共属性。我正在执行涉及代码中其他地方此属性的 Linq 查询。
现在,进行 100,000 次迭代(甚至不是一百万次),我可以看到,如果我也将具有相同名称的属性添加到派生类中(智能感知使用工具提示"此属性隐藏继承成员"突出显示它),从而基本上使"快捷方式"(但也是属性的重复) - 代码运行速度明显更快......对我来说,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,当您的代码使用派生类的本地属性时,它会生成一个call
而不是一个callvirt
,这显然更便宜。
这似乎是一个过早的优化,除非您处于时间关键循环中。
在使用 linq 构建迭代时担心call
和callvirt
性能之间的差异似乎......特别为时过早。
当您隐藏属性时,您将它设置为非虚拟调用,而不是对成员的虚拟调用。 虚拟调度确实有一定的成本与之相关,这就是为什么您可以声明非虚拟属性/方法的原因。
也就是说,在大多数应用程序中,与虚拟方法/属性相关的成本根本不是问题。 是的,这是有区别的,但在大多数程序的上下文中根本没有太大区别。
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
方法。Title
和AnotherTitle
看起来都像
IL_0008: callvirt instance string ConsoleApplication4.Program/ContentItem::get_Title()
IL_0016: callvirt instance string ConsoleApplication4.Program/Channel::get_AnotherTitle()
您遇到的问题与call
或callvirt
无关。可能区别在于代码,您没有向我们展示。