我很想知道从方法返回值或通过Action参数返回值之间的性能差异。
这有一个相关的问题 Performance of calling delegates vs methods
但是对于我的生活,我无法解释为什么返回一个值比调用一个委托返回值慢约30%。是.net Jitter(不是编译器..)内置我的简单委托(我不认为它那样做了)?
class Program
{
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
sw.Start();
A aa = new A();
long l = 0;
for( int i = 0; i < 100000000; i++ )
{
aa.DoSomething( i - 1, i, r => l += r );
}
sw.Stop();
Trace.WriteLine( sw.ElapsedMilliseconds + " : " + l );
sw.Reset();
sw.Start();
l = 0;
for( int i = 0; i < 100000000; i++ )
{
l += aa.DoSomething2( i - 1, i );
}
sw.Stop();
Trace.WriteLine( sw.ElapsedMilliseconds + " : " + l );
}
}
class A
{
private B bb = new B();
public void DoSomething( int a, int b, Action<long> result )
{
bb.Add( a,b, result );
}
public long DoSomething2( int a, int b )
{
return bb.Add2( a,b );
}
}
class B
{
public void Add( int a, int b, Action<long> result )
{
result( a + b );
}
public long Add2( int i, int i1 )
{
return i + i1;
}
}
答案 0 :(得分:2)
我对您的代码进行了一些更改。
new A()
。Action<long>
引用,这样就不必在每次迭代时创建它。这个似乎对执行时间有很大的影响。以上是我做出上述更改后的结果。 vshost列指示代码是否在vshost.exe进程内执行(通过直接从Visual Studio运行)。我使用的是Visual Studio 2008,目标是.NET 3.5 SP1。
vshost? Debug Release
-------------------------
YES 6405 3827
11059 3092
NO 4214 1691
4607 811
请注意,根据构建配置和执行环境,您会得到不同的结果。如果没别的话,结果很有意思。如果我有时间,我可以编辑我的答案以提供理论。
答案 1 :(得分:1)
奇怪的是,在VS中运行Release版本时,我没有看到您所描述的行为。我在运行Debug构建时看到它。我唯一能想到的是,在运行Debug构建时,基于返回的方法会增加额外开销,尽管我不够聪明,不知道为什么。
以下是其他有趣的内容:当我切换到x64版本(发布或调试)时,这种差异就会消失。
如果我冒险猜测(完全未经证实),可能是将64位long
作为返回值传递的成本B.Add2
和A.DoSomething2
中的Action<long>
超过了在32位环境中传递Action<long>
的重量。在64位环境中,这种节省将消失,因为long
也需要64位。在任一配置的发布版本中,传递B.Add2
的成本可能会消失,因为A.DoSomething2
和{{1}}似乎都是内联的主要候选者。
有些人比我更了解这一点:我可以完全驳斥我刚才所说的一切。毕竟,我们都在这里学习;)
答案 2 :(得分:1)
对于初学者来说,您对new A()
的呼叫正在按照您当前设置代码的方式进行计时。您需要确保在发布模式下运行并进行优化。此外,您还需要考虑JIT - 填写所有代码路径,这样您就可以保证在计算之前编译它们(除非您关注启动时间)。
当你尝试对大量原始操作(简单添加)进行计时时,我发现了一个问题。在这种情况下,您无法做出明确的结论,因为任何开销都将完全控制您的测量。
编辑: 在VS2008中针对.NET 3.5的发布模式中,我得到:
1719 : 9999999800000000
1337 : 9999999800000000
这似乎与许多其他答案一致。使用ILDasm为B.Add提供以下IL:
IL_0000: ldarg.3
IL_0001: ldarg.1
IL_0002: ldarg.2
IL_0003: add
IL_0004: conv.i8
IL_0005: callvirt instance void class [mscorlib]System.Action`1<int64>::Invoke(!0)
IL_000a: ret
B.Add2是:
IL_0000: ldarg.1
IL_0001: ldarg.2
IL_0002: add
IL_0003: conv.i8
IL_0004: ret
所以看起来好像只是计算load
和callvirt
。
答案 3 :(得分:-1)
为什么不使用reflector来查找?