什么时候通过值传递结构更有效率?何时通过C#中的ref传递结果?

时间:2017-09-19 04:28:15

标签: c# performance struct ref

我已经研究了一下,似乎常识认为结构应该在16字节以下,否则它们会导致复制的性能损失。使用C#7和ref返回,完全避免完全避免复制结构变得非常容易。我假设随着结构大小变小,传递ref会产生更多的开销,只需复制值。

是否有关于按值传递结构的速度比ref更快的经验法则?哪些因素会影响这个? (结构大小,过程位数等)

更多背景信息: 我正在开发一款游戏,绝大多数数据都表示为连续的结构数组,以实现最大的缓存友好性。正如您可能想象的那样,在这种情况下传递结构很常见。我知道分析是确定某些内容的性能影响的唯一真正方法。但是,我想了解它背后的理论概念,并希望在编写代码时考虑到这种理解,并仅描述边缘情况。

另外,请注意,我并没有询问最佳做法或通过ref传递所有内容的理智。我了解"最佳做法"和影响,我故意选择不遵循它们。

编辑 - 解决"重复"标签

Pass by value vs Pass by reference performance C#.net - 这个问题讨论了通过引用传递引用类型,这与我要求的完全不同。

In .Net, when if ever should I pass structs by reference for performance reasons? - 第二个问题涉及到主题,但它涉及结构的特定大小。

回答Eric Lippert's article的问题:

你真的需要回答这个问题吗?我是的。因为它会影响我编写大量代码的方式。

这真的是瓶颈吗?可能不是。但我仍然想知道,因为这是99%的程序的数据访问模式。在我看来,这类似于选择正确的数据结构。

差异是否相关?确实如此。通过ref传递大型结构更快。我只想了解这方面的局限。

你说的“更快”是什么?就像为同一任务减少CPU的工作量一样。

你是在看大局吗?是的。如前所述,它会影响我写整篇文章的方式。

你为什么要问我?因为SO应该是Q& A网站。

我知道我可以测量很多不同的组合。这告诉我什么?在我的[.NET版本,进程位,操作系统,CPU]的组合中,X比Y更快。 Linux怎么样? Android怎么样? iOS怎么样? 我应该对所有可能的硬件/软件组合的所有排列进行基准测试吗?

我认为这不是一个可行的策略。因此,我在这里问一下,对CLR / JIT / ASM / CPU有很多了解的人可以告诉我它是如何工作的,这样我就可以在编写代码时做出明智的决定。

我正在寻找的答案类似于前面提到的结构大小的16字节指南,并解释了原因。

3 个答案:

答案 0 :(得分:6)

通常,通过引用传递应该更快。
通过引用传递结构时,仅传递指向该结构的指针,该指针仅为32/64位整数。
按值传递结构时,需要复制整个结构,然后将指针传递给新副本。
除非结构非常小(例如int),否则按引用传递会更快。

此外,按值传递将增加对os进行内存分配和取消分配的调用次数,这些调用非常耗时,因为os必须检查注册表中的可用空间。

答案 1 :(得分:3)

如果通过引用传递结构,则它们可以是任何大小。您仍在处理8(假定为x64)字节的指针。为了获得最高的性能,您需要一个友好的CPU缓存设计,即数据驱动设计。

游戏通常使用一种称为实体组件系统的特殊数据驱动设计。请参见Konrad Kokosa第14章的 Pro .NET内存管理书。

基本思想是,您可以更新自己的游戏实体,例如Movable,Car,Plane ...共享通用属性,例如位置,该位置适用于存储在连续数组中的所有实体。如果您需要增加1K实体的位置,则只需要查找所有实体的位置数组的数组索引并在那里进行更新。这样可以提供最佳的数据局部性。如果将所有内容存储在类中,则每个类实例的许多this指针将丢失CPU预取器。

有关此参考架构,请参阅此英特尔文章:https://software.intel.com/en-us/articles/get-started-with-the-unity-entity-component-system-ecs-c-sharp-job-system-and-burst-compiler

那里有很多实体组件系统,但是到目前为止,我还没有看到使用引用结构作为其主要工作数据结构的实体。原因是所有流行的代码都比引入引用结构的C#7.2长得多。

答案 2 :(得分:0)

我终于找到了答案。断点是 System.IntPtr.Size 。用微软自己的话说,Write safe and efficient C# code

添加in修饰符以通过引用传递参数,并声明您的设计意图通过引用传递参数以避免不必要的复制。您无意修改用作该参数的对象。

对于大于IntPtr.Size的只读值类型,此做法通常可以提高性能。对于简单类型(sbyte,byte,short,ushort,int,uint,long,ulong,char,float,double,decimal和bool和enum类型),任何潜在的性能提升都是最小的。实际上,对于小于IntPtr.Size的类型使用传递引用可能会降低性能。