C#中的out参数是否对我应该了解的任何性能影响?(如例外)
out
我的意思是,在一个每秒运行几百万次的循环中有一个带有参数的方法是个好主意吗?
我知道它很难看,但我使用它的方式与Int32.TryParse
使用它们的方式相同——返回 abool
以告知某些验证是否成功,如果成功,则有一个out
包含一些附加数据的参数。
C#中的out参数是否对我应该了解的任何性能影响?(如例外)
out
我的意思是,在一个每秒运行几百万次的循环中有一个带有参数的方法是个好主意吗?
我知道它很难看,但我使用它的方式与Int32.TryParse
使用它们的方式相同——返回 abool
以告知某些验证是否成功,如果成功,则有一个out
包含一些附加数据的参数。
我怀疑您会发现使用out
参数会导致任何显着的性能损失。您必须以某种方式将信息返回给呼叫者 -out
只是一种不同的方式。如果您在方法中广泛使用 out 参数,您可能会发现会有一些损失,因为这很可能意味着每次访问都需要额外的重定向级别。但是,我不认为它会很重要。像往常一样,编写最易读的代码并测试性能是否已经足够好,然后再尝试进一步优化。
编辑:其余的内容是有效的。它只与大值类型真正相关,通常应该避免这种情况:)
不过,我不同意 Konrad 关于“所有类型 > 32 位的返回值的处理方式与机器级别的输出参数相似或相同”的断言。这是一个小测试应用程序:
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
struct BigStruct
{
public Guid guid1, guid2, guid3, guid4;
public decimal dec1, dec2, dec3, dec4;
}
class Test
{
const int Iterations = 100000000;
static void Main()
{
decimal total = 0m;
// JIT first
ReturnValue();
BigStruct tmp;
OutParameter(out tmp);
Stopwatch sw = Stopwatch.StartNew();
for (int i=0; i < Iterations; i++)
{
BigStruct bs = ReturnValue();
total += bs.dec1;
}
sw.Stop();
Console.WriteLine("Using return value: {0}",
sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i=0; i < Iterations; i++)
{
BigStruct bs;
OutParameter(out bs);
total += bs.dec1;
}
Console.WriteLine("Using out parameter: {0}",
sw.ElapsedMilliseconds);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static BigStruct ReturnValue()
{
return new BigStruct();
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static void OutParameter(out BigStruct x)
{
x = new BigStruct();
}
}
结果:
Using return value: 11316
Using out parameter: 7461
基本上通过使用 out 参数,我们将数据直接写入最终目的地,而不是将其写入小方法的堆栈帧,然后将其复制回 Main 方法的堆栈帧。
不过,请随意批评基准应用程序 - 我可能错过了一些东西!
没有性能影响。out
从技术角度来看,与任何旧的参数传递基本相同。虽然复制大量数据(例如对于大型结构)听起来似乎是合理的,但这实际上与返回值相同。
事实上,所有大于 32 位的类型的返回值的处理方式都类似于机器级别out
的参数。
请注意,最后一条语句并不建议out
在 .NET 中返回 value == 参数。Jon 的基准测试表明,显然(并且令人遗憾地)情况并非如此。事实上,为了使其相同,在 C++ 编译器中使用了命名返回值优化。在 JIT 的未来版本中可能会执行类似的操作,以提高返回大型结构的性能(但是,由于大型结构在 .NET 中非常罕见,这可能是不必要的优化)。
但是,(而且我对 x86 汇编的了解非常有限),从函数调用返回对象通常需要在调用站点分配足够的空间,将地址压入堆栈并通过将返回值复制到其中来填充它。这基本上是一样的out
,只是省略了值的不必要的临时副本,因为可以直接访问目标内存位置。
不是性能问题,而是较早出现的问题 -你不能在 C# 4.0 中使用它们。
就个人而言,我倾向于在我的私有out
代码中大量使用参数(即在一个类中,有一个方法可以返回多个值而不使用单独的类型)——但我倾向于在公共 API 上避免使用它们,模式除外。bool Try{Something}(out result)
避免输出参数的主要原因是代码可读性,而不是性能。
对于值类型,无论如何都没有真正的区别(它们总是复制),对于引用类型,它基本上与通过 ref 传递相同。
十有八九你最好创建自己的哑记录类,而不是使用 out 参数 - 当你稍后返回代码时,这更容易阅读和理解。
Out 参数由 ref 传递。所以只有一个指针在堆栈上传递。
如果您的值类型很大,则副本较少,但是您必须取消引用每个变量使用的指针。
使用 out 参数不会损害性能。Out 参数基本上是一个引用参数,因此调用者和被调用者都指向同一块内存。