2014-03-07 68 views
15

我正在优化我的代码,并且我注意到使用属性(甚至是自动属性)对执行时间有着深远的影响。看下面的例子:使用属性和性能

[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 optimizerinlined

+9

当你说“在发布模式下”你是指release * build *配置,还是在没有调试器的情况下运行?如果你在调试器中运行,我完全希望看到一个重大的打击。还要注意,一个循环很不寻常,因为这个循环很紧张......并且合理地微调优化*仅仅是那些被证明是瓶颈的应用程序部分。 –

+8

我刚刚测试了自己的代码,x86 JIT使属性访问与字段访问基本相同。 x64 JIT显示了你在问题中的行为。您可能想尝试即将推出的新x64 JIT:http://blogs.msdn.com/b/dotnet/archive/2014/02/27/ryujit-ctp2-getting-ready-for-prime- time.aspx –

+0

@JonSkeet,我的意思是发布版本配置。我从ReSharper测试跑步者那里运行这些测试是精确的。 – Grzenio

回答

-1

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

  1. 如果未选中,进入属性仍然方法调用
  2. 如果检查属性在内衬和性能是一样的,可直接现场访问,因为JIT编译的代码会相同

对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。 如果你的财产将返回引用类型,属性getter将被内联。 这意味着int NumRepet { get; set; }没有内联,但object NumRepet { get; set; }将内联,如果你不打破另一个限制。

X64 JIT的优化是很差,这就是为什么新的将被引入约翰提

+1

不正确。默认情况下,发布版本会检查“优化代码”,即使使用优化的代码,也会出现性能问题。 – ken2k

+0

我编辑了我关于X64的帖子JIT – Aik

+0

“如果检查到属性是内联的并且性能相同”仍然不正确,则可以使用当前的x64 JIT编译器对其进行测试。 – ken2k

0

你说你要优化你的代码,但我很好奇,怎么样,有什么功能应该是,以及源数据是什么以及它的大小,因为这显然不是“真实”的代码。如果您在分析大量数据时考虑使用BinarySearch功能。这比使用非常大的数据集的.Contains()函数要快得多。

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

也许你只是循环浏览数据。如果您正在循环数据并返回一个值,那么您可能需要使用yield关键字。另外,如果可以的话,考虑潜在的并行库的使用,或者利用您自己的线程管理。

这看起来不像你想要的来源,但它是非常通用的,所以我想这是值得一提的。

public IEnumerable<int> 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; 
} 
+0

在我们的案例中,并行化是在更高层次上完成的,所以我优化的代码应该保持单线程。尽管谢谢你的一个有效的观点! – Grzenio

1

getter/setter方法是用一些特殊约定方法语法糖(在二传手“值”变量“并没有明显的参数列表)。

根据这一article,”如果有任何的该方法的正式参数是结构体,方法不会被内联。“ - 整数是结构体,因此我认为这个限制是适用的。

我没有看过由以下代码制作的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; 
     } 
    } 
} 

这将产生..在我的机器,OS X(新版本的单声道),这些结果(以秒为单位):

  • 使用字段:2.606
  • 使用盒装的getter:2.585
  • 使用未装箱的getter:2.71
+0

在Windows(.net v4.5,x64)上,我没有得到有意义的,或重复性的时间差异。由于JIT开销(尤其是对于持续时间仅为50毫秒的“小”运行),更改基准的顺序也会影响结果。 –

-1

,当它完成了循环,当你正在写来安慰这个可以添加额外的时间会扭曲你的结果你的秒表仍在运行,应停止秒表。

[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); 
} 
+0

小心解释反对票吗? – Marko

+0

在方法运行之前评估该方法的参数。评估该属性的价值是在该行代码上执行的第一件事情之一。停止秒表甚至可能需要更多时间。最重要的是,两种解决方案之间的差异将实际为零,即使速度较慢,也会如此。 – Servy

1

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

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

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