【问题标题】:In .Net, when if ever should I pass structs by reference for performance reasons?在 .Net 中,出于性能原因,我何时应该通过引用传递结构?
【发布时间】:2010-10-03 15:18:29
【问题描述】:

在我的 C# 应用程序中,我有一个大型结构(176 字节),每秒可能向函数传递十万次。然后,此函数只需获取指向结构的指针并将指针传递给非托管代码。函数和非托管代码都不会对结构进行任何修改。

我的问题是,我应该通过值还是通过引用将结构传递给函数?在这种特殊情况下,我的猜测是通过引用传递比将 176 个字节推入调用堆栈要快得多,除非 JIT 碰巧认识到该结构从未被修改过(我的猜测是它无法识别这一点,因为结构的地址被传递给非托管代码)并优化代码。

既然我们在这里,让我们也回答更一般的情况,即函数将结构的指针传递给非托管代码,而是对结构。通过引用传递结构会更快吗?在这种情况下,JIT 是否会认识到该结构从未被修改并因此进行优化?据推测,通过引用传递一个 1 字节的结构不是更有效,但是在多大的结构大小下,通过引用传递一个结构会变得更好,如果有的话?

谢谢。

编辑:

如下所述,还可以创建一个“等效”类以供常规使用,然后在传递给非托管代码时使用结构。我在这里看到两个选项:

1) 创建一个仅包含该结构的“包装器”类,然后在必要时将指向该结构的指针固定并传递给非托管代码。我看到的一个潜在问题是固定有其自身的性能影响。

2) 创建一个等效类,当需要结构体时,其字段被复制到结构体中。但是复制会花费很多时间,而且在我看来,首先要打破通过引用传递的意义。

编辑:

正如下面多次提到的,我当然可以衡量每种方法的性能。我这样做并发布结果。但是,我仍然有兴趣从知识的角度看待人们的答案和推理。

【问题讨论】:

  • 有趣的问题。当然,明显的反应是每个人的情况都不一样,你不应该过早地优化,如果你想提高性能,你应该分析你的应用程序。也就是说,我很好奇一些真实的答案。
  • 这是一个相当大的结构,为什么不把它转换成一个类呢?如果您要通过 ref 传递它,那么无论如何它都会破坏使其成为结构的目的。
  • 指向该结构的指针被直接传递给硬件设备驱动程序,所以是的,它确实需要是一个结构。我认为,将其保留为结构并简单地获取指向它的指针比编组等效对象要快得多。
  • 如果它没有被修改,我不得不期望对象的单个固定实例会快得多。更不用说结构对 x64 JITter 造成的不良影响了。

标签: c# .net optimization


【解决方案1】:

我做了一些非常非正式的分析,结果表明,对于我的特定应用程序,通过引用传递有适度的性能提升。对于按值,我每秒收到大约 10,050,000 个调用,而对于按引用,我每秒收到大约 11,200,000 个调用。

您的里程可能会有所不同。

【讨论】:

  • 你的结构体的大小是多少?
  • 每个 176 字节。 (它们通过指针传递给图形驱动程序,这就是为什么它们需要是结构而不是类的原因。)
【解决方案2】:

在你问是否应该通过引用传递结构之前,你应该先问问自己为什么你有这么大的结构。它真的需要是一个结构吗?如果您需要在某个时候为 P/Invoke 使用一个结构,是否值得为此使用一个结构 just,然后在其他地方使用等效的类?

这么大的结构非常非常不寻常......

请参阅Choosing Between Classes and Structures 上的Design Guidelines for Developing Class Libraries 部分,了解有关此方面的更多指导。

【讨论】:

  • 结构周期很不寻常
  • 这个特定的结构被直接传递给硬件设备驱动程序,所以是的,在我的例子中它确实需要是一个结构。我现在还想到,我可以将结构包装在一个类中,然后在需要指针时固定结构,但固定可能会引入其他性能问题。
  • 我会更多地关注具有用于 P/Invoke 的结构和用于常规使用的 cals 的选项(Jon 提到的)。使用一些隐式转换运算符,它可以很好地工作。
  • 该结构可能包含其他结构,而这些结构又可能包含其他结构——这样的野兽当然可以非常大。
  • @Justice:在这种情况下,它应该仅用于 P/Invoke,或者应该重新设计。 “非常大”的结构是一种很大的设计气味。
【解决方案3】:

获得此问题答案的唯一方法是将两者都编写代码并衡量性能。

您提到了非托管/托管互操作。我的经验是,互操作需要很长时间。您可以尝试从以下位置更改您的代码:

void ManagedMethod(MyStruct[] items) {
  foreach (var item in items) {
    unmanagedHandle.ProcessOne(item);
  }
}

收件人:

void ManagedMethod(MyStruct[] items) {
  unmanagedHandle.ProcessMany(items, items.Count);
}

这种技术在类似的情况下帮助了我,但只有测量才能告诉你它是否适用于你的情况。

【讨论】:

  • 我认为你误解了我的特殊情况。我只传递一个项目,而不是一个项目数组,尽管每秒传递很多次。
【解决方案4】:

为什么不直接使用类,然后将您的类传递给您的 P/Invoke 函数?

使用类将在您的托管代码中很好地传递,并且与通过引用 P/Invoke 函数传递结构相同。

例如

// What you have
public struct X
{
   public int data;
}
[DllImport("mylib.dll")]
static extern void Foo( ref X arg);

// What you could do
[StructLayout(LayoutKind.Sequential)]
public class Y
{
    public int data;
}
[DllImport("mylib.dll")]
static extern void Bar( Y arg );

【讨论】:

  • 为什么投反对票。这是错的吗?我们可以帮助贡献者修复答案吗?
猜你喜欢
  • 2011-08-16
  • 1970-01-01
  • 2013-06-04
  • 2012-05-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-13
相关资源
最近更新 更多