使用属性和性能

我正在优化我的代码,我注意到使用属性(甚至是自动属性)会对执行时间产生深远的影响。 请参阅以下示例:

[Test] public void GetterVsField() { PropertyTest propertyTest = new PropertyTest(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); propertyTest.LoopUsingCopy(); Console.WriteLine("Using copy: " + stopwatch.ElapsedMilliseconds / 1000.0); stopwatch.Restart(); propertyTest.LoopUsingGetter(); Console.WriteLine("Using getter: " + stopwatch.ElapsedMilliseconds / 1000.0); stopwatch.Restart(); propertyTest.LoopUsingField(); Console.WriteLine("Using field: " + stopwatch.ElapsedMilliseconds / 1000.0); } public class PropertyTest { public PropertyTest() { NumRepet = 100000000; _numRepet = NumRepet; } int NumRepet { get; set; } private int _numRepet; public int LoopUsingGetter() { int dummy = 314; for (int i = 0; i < NumRepet; i++) { dummy++; } return dummy; } public int LoopUsingCopy() { int numRepetCopy = NumRepet; int dummy = 314; for (int i = 0; i < numRepetCopy; i++) { dummy++; } return dummy; } public int LoopUsingField() { int dummy = 314; for (int i = 0; i < _numRepet; i++) { dummy++; } return dummy; } } 

在我的机器上的Release模式中,我得到:

 Using copy: 0.029 Using getter: 0.054 Using field: 0.026 

在我的情况下是一个灾难 – 如果我想获得最大的性能,最关键的循环就不能使用任何属性。

我在这做错了什么? 我认为这些将由JIT optimizer inlined

Getters / Setters是具有一些特殊约定的方法的语法糖(setter中的“value”变量“,并且没有可见的参数列表)。

根据这篇文章 ,“如果任何方法的forms参数都是结构体,那么该方法将不会被内联。” – 整数是结构。 因此,我认为这个限制适用。

我没有看过以下代码生成的IL,但我确实得到了一些有趣的结果,我认为这样做是这样的……

 using System; using System.Diagnostics; public static class Program{ public static void Main() { PropertyTest propertyTest = new PropertyTest(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); propertyTest.LoopUsingField(); Console.WriteLine("Using field: " + stopwatch.ElapsedMilliseconds / 1000.0); stopwatch.Restart(); propertyTest.LoopUsingBoxedGetter(); Console.WriteLine("Using boxed getter: " + stopwatch.ElapsedMilliseconds / 1000.0); stopwatch.Restart(); propertyTest.LoopUsingUnboxedGetter(); Console.WriteLine("Using unboxed getter: " + stopwatch.ElapsedMilliseconds / 1000.0); } } public class PropertyTest { public PropertyTest() { _numRepeat = 1000000000L; _field = 1; Property = 1; IntProperty = 1; } private long _numRepeat; private object _field = null; private object Property {get;set;} private int IntProperty {get;set;} public void LoopUsingBoxedGetter() { for (long i = 0; i < _numRepeat; i++) { var f = Property; } } public void LoopUsingUnboxedGetter() { for (long i = 0; i < _numRepeat; i++) { var f = IntProperty; } } public void LoopUsingField() { for (long i = 0; i < _numRepeat; i++) { var f = _field; } } } 

这产生.. ON MY MACHINE,OS X(Mono的最新版本),这些结果(以秒为单位):

  • 使用字段:2.606
  • 使用盒装吸气剂:2.585
  • 使用未装箱的吸气剂:2.71

遵循80/20性能规则而不是微优化。 编写代码以实现可维护性,而不是性能。 也许汇编语言是最快的,但这并不意味着我们应该将汇编语言用于所有目的。

您正在运行循环1亿次,差异为0.02毫秒或20微秒。 调用函数会产生一些开销,但在大多数情况下并不重要。 您可以信任编译器内联或执行高级操作。

直接访问该字段在99%的情况下都会出现问题,因为您无法控制所有变量的引用位置,并在发现错误时在太多位置进行修复。

你说你正在优化你的代码,但我很好奇它是如何,function应该是什么,以及进入它的源数据是什么以及它的大小,因为这显然不是“真正的”代码。 如果您正在考虑使用BinarySearchfunction来解析大量数据。 这比使用非常大的数据集的.Contains()函数快得多。

 List myList = GetOrderedList(); if (myList.BinarySearch(someValue) < 0) // List does not contain data 

也许你只是简单地循环数据。 如果您循环遍历数据并返回值,则可能需要使用yield关键字。 另外,如果可以,请考虑并行库的潜在用途,或者使用自己的线程管理。

这似乎不是你想要根据发布的来源判断,但它是非常通用的所以我认为这是值得一提的。

 public IEnumerable LoopUsingGetter() { int dummy = 314; for (int i = 0; i < NumRepet; i++) { dummy++; yield return dummy; } } [ThreadStatic] private static int dummy = 314; public static int Dummy { get { if (dummy != 314) // or whatever your condition { return dummy; } Parallel.ForEach (LoopUsingGetter(), (i) { //DoWork(), not ideal for given example, but due to the generic context this may help dummy += i; }); } return dummy; } 

您必须检查是否选中了优化代码复选框。

  1. 如果未选中,则仍然可以通过方法调用访问该属性
  2. 如果选中该属性是内联的,并且性能与直接字段访问相同,因为JITed代码将是相同的

在X64 JIT编译器中有更多关于inlinig的限制。 有关JIT64内联优化的更多信息,请访问: http : //blogs.msdn.com/b/davbr/archive/2007/06/20/tail-call-jit-conditions.aspx

请参阅#3 The caller or callee return a value type#3 The caller or callee return a value type 。 如果您的属性将返回引用类型,则属性getter将为内联。 这意味着属性int NumRepet { get; set; } int NumRepet { get; set; } 没有内联但是object NumRepet { get; set; } object NumRepet { get; set; } 如果你不打破另一个限制,将被内联。

X64 JIT的优化很差,这就是John提到的新引入的原因

您应该在完成循环时停止秒表,当您写入控制台时,秒表仍然在运行,这可能会增加额外的时间,从而导致结果出现偏差。

 [Test] public void GetterVsField() { PropertyTest propertyTest = new PropertyTest(); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); propertyTest.LoopUsingCopy(); stopwatch.Stop(); Console.WriteLine("Using copy: " + stopwatch.ElapsedMilliseconds / 1000.0); stopwatch.Reset(); stopwatch.Start(); propertyTest.LoopUsingGetter(); stopwatch.Stop(); Console.WriteLine("Using getter: " + stopwatch.ElapsedMilliseconds / 1000.0); stopwatch.Reset(); stopwatch.Start(); propertyTest.LoopUsingField(); stopwatch.Stop(); Console.WriteLine("Using field: " + stopwatch.ElapsedMilliseconds / 1000.0); }