请注意此问题仅与演出有关。让我们跳过设计指南,理念,兼容性,可移植性以及与纯性能无关的任何内容。谢谢。
现在回答这个问题。我一直认为,因为C#getters / setter实际上是伪装的方法,所以读取公共字段必须比调用getter更快。
所以要确保我做了一个测试(下面的代码)。但是,此测试仅产生预期结果(即字段比34%的getter更快)如果从Visual Studio内部运行它。
从命令行运行它后,显示几乎相同的时间......
唯一的解释可能是CLR会进行额外的优化(如果我在这里错了,请纠正我)。
我不相信在实际应用中,以更复杂的方式使用这些属性,它们将以相同的方式进行优化。
请帮助我证明或反驳现实生活中的属性比田地慢的想法。
问题是 - 如何修改测试类以使CLR更改行为,以便公共字段超越getter。或者告诉我,任何没有内部逻辑的属性都会像字段一样执行(至少在getter上)
编辑:我只谈论发布x64版本。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace PropertyVsField
{
class Program
{
static int LEN = 20000000;
static void Main(string[] args)
{
List<A> a = new List<A>(LEN);
List<B> b = new List<B>(LEN);
Random r = new Random(DateTime.Now.Millisecond);
for (int i = 0; i < LEN; i++)
{
double p = r.NextDouble();
a.Add(new A() { P = p });
b.Add(new B() { P = p });
}
Stopwatch sw = new Stopwatch();
double d = 0.0;
sw.Restart();
for (int i = 0; i < LEN; i++)
{
d += a[i].P;
}
sw.Stop();
Console.WriteLine("auto getter. {0}. {1}.", sw.ElapsedTicks, d);
sw.Restart();
for (int i = 0; i < LEN; i++)
{
d += b[i].P;
}
sw.Stop();
Console.WriteLine(" field. {0}. {1}.", sw.ElapsedTicks, d);
Console.ReadLine();
}
}
class A
{
public double P { get; set; }
}
class B
{
public double P;
}
}
答案 0 :(得分:51)
正如其他人已经提到的那样,getter是内联。
如果你想避免内联,你必须
用手动属性替换自动属性:
class A
{
private double p;
public double P
{
get { return p; }
set { p = value; }
}
}
并告诉编译器不要内联getter(或两者,如果你愿意的话):
[MethodImpl(MethodImplOptions.NoInlining)]
get { return p; }
请注意,第一个更改不会对性能产生影响,而第二个更改会显示明确的方法调用开销:
手动属性:
auto getter. 519005. 10000971,0237547.
field. 514235. 20001942,0475098.
没有内联吸气剂:
auto getter. 785997. 10000476,0385552.
field. 531552. 20000952,077111.
答案 1 :(得分:24)
查看来自MSDN上的一个VB团队成员的Properties vs Fields – Why Does it Matter? (Jonathan Aneja)博客文章。他概述了属性与字段的参数,并解释了如下的微不足道的属性:
我听说过在属性上使用字段的一个论点是 “字段更快”,但对于实际上没有的琐碎属性 是的,因为CLR的Just-In-Time(JIT)编译器将内联 属性访问并生成与访问a一样高效的代码 直接领域。
答案 2 :(得分:12)
JIT将内联任何方法(不仅仅是一个getter),其内部指标确定的内联更快。鉴于标准属性为return _Property;
,它将在每种情况下都内联。
您看到不同行为的原因是,在附加调试器的调试模式下,JIT非常缺陷,以确保任何堆栈位置与您对代码的期望相匹配。
你也忘记了表现的头号规则,测试节拍思维。例如,即使快速排序渐远比插入排序快,但对于极小的输入,插入排序实际上更快。
答案 3 :(得分:6)
唯一可能的解释是CLR会进行额外的优化(如果我在这里错了,请与我联系)。
是的,它被称为内联。它在编译器中完成(机器代码级 - 即JIT)。由于getter / setter是微不足道的(即非常简单的代码),方法调用被破坏,getter / setter被写入周围的代码中。
在调试模式下不会发生这种情况以支持调试(即在getter或setter中设置断点的能力)。
在visual studio中,无法在调试器中执行此操作。编译版本,在没有附加调试器的情况下运行,您将获得完整的优化。
我不相信在实际应用中,以更复杂的方式使用这些属性,它们将以相同的方式进行优化。
世界充满了错误的幻想。它们将被优化,因为它们仍然是微不足道的(即简单的代码,因此它们被内联)。
答案 4 :(得分:3)
应该注意的是,可以在Visual Studio中看到“真实”的性能。
现在即使连接了调试器,jitted程序集也是一样的,如果你愿意,可以让你进入优化的反汇编程序。这对于理解CLR如何优化代码至关重要。
答案 5 :(得分:0)
阅读完所有文章后,我决定使用以下代码作为基准:
<%= text_field(:doc, :email, {:value=>current_user.try(:email),:class=>"form-control"})%>
在调试模式下进行测试时,我得到了以下结果:
[TestMethod]
public void TestFieldVsProperty()
{
const int COUNT = 0x7fffffff;
A a1 = new A();
A a2 = new A();
B b1 = new B();
B b2 = new B();
C c1 = new C();
C c2 = new C();
D d1 = new D();
D d2 = new D();
Stopwatch sw = new Stopwatch();
long t1, t2, t3, t4;
sw.Restart();
for (int i = COUNT - 1; i >= 0; i--)
{
a1.P = a2.P;
}
sw.Stop();
t1 = sw.ElapsedTicks;
sw.Restart();
for (int i = COUNT - 1; i >= 0; i--)
{
b1.P = b2.P;
}
sw.Stop();
t2 = sw.ElapsedTicks;
sw.Restart();
for (int i = COUNT - 1; i >= 0; i--)
{
c1.P = c2.P;
}
sw.Stop();
t3 = sw.ElapsedTicks;
sw.Restart();
for (int i = COUNT - 1; i >= 0; i--)
{
d1.P = d2.P;
}
sw.Stop();
t4 = sw.ElapsedTicks;
long max = Math.Max(Math.Max(t1, t2), Math.Max(t3, t4));
Console.WriteLine($"auto: {t1}, {max * 100d / t1:00.00}%.");
Console.WriteLine($"field: {t2}, {max * 100d / t2:00.00}%.");
Console.WriteLine($"manual: {t3}, {max * 100d / t3:00.00}%.");
Console.WriteLine($"no inlining: {t4}, {max * 100d / t4:00.00}%.");
}
class A
{
public double P { get; set; }
}
class B
{
public double P;
}
class C
{
private double p;
public double P
{
get => p;
set => p = value;
}
}
class D
{
public double P
{
[MethodImpl(MethodImplOptions.NoInlining)]
get;
[MethodImpl(MethodImplOptions.NoInlining)]
set;
}
}
但是当切换到释放模式时,结果与以前不同。
auto: 35142496, 100.78%.
field: 10451823, 338.87%.
manual: 35183121, 100.67%.
no inlining: 35417844, 100.00%.
似乎自动属性是一种更好的方法。