【问题标题】:Should I Make These Vectors Classes or Structs in C#我应该在 C# 中创建这些向量类还是结构
【发布时间】:2023-04-08 20:02:01
【问题描述】:

我正在用 C# 创建一个几何库,我需要以下 不可变 类型:

  • Vector2f(2 floats - 8 个字节)
  • Vector2d (2 doubles - 16 字节)
  • Vector3f (3 floats - 12 字节)
  • Vector3d (3 doubles - 24 字节)
  • Vector4f (4 floats - 16 字节)
  • Vector4d (4 doubles - 32 字节)

我正在尝试确定是使它们成为结构还是类。 MSDN 建议仅在大小不超过 16 个字节时才使用结构。该参考似乎来自 2005 年。16 字节仍然是建议的最大大小吗?

我确信将结构用于float 向量会比使用类更有效,但我应该如何处理double 向量?我应该让它们的结构也保持一致,还是应该让它们成为类?

更新: 看起来每个人都同意他们应该是结构。感谢您的精彩回答。

【问题讨论】:

  • 如果你在做 OpenGL 的东西,就用 OpenTK

标签: c# class struct performance size


【解决方案1】:

Microsoft 的 XNA 框架为其 Vector2/3/4 数据类型使用结构。这些包含float 类型的字段。我认为这样做没有任何问题。使用 double 类型的字段应该不会有很大的不同。

【讨论】:

  • WPF 的 Vector/Rect/Points 也使用结构体,其中包含双字段而不是浮点数(在 Point4D 的情况下最多包括 4 个字段)
【解决方案2】:

我通常会制作像这些结构这样的类型。结构更有可能被放置在堆栈上,如果你对它们使用数组,你可以获得比使用 List 对象更好的性能。只要您远离会导致矢量类被装箱的操作,结构通常是性能更高的方法。

【讨论】:

    【解决方案3】:

    不可变结构是一个不错的选择。 MSDN上推荐的大小是最弱的,32字节不算大。

    主要论点是向量像简单(数字)类型一样使用,并且作为 值类型 的实现是最合适的。

    dotNet 4 中的新 ComplexBigInteger 结构是一个很好的平行结构

    【讨论】:

      【解决方案4】:

      结构,当然。为什么?嗯,我猜这是感觉的一部分,一个向量的感觉和行为就像一个

      Rico Mariani here 给出了为 API 的某些类型选择值类型的一些原因(我认为他说的不是 XNA)。

      虽然效率是这里的一个因素,但我不认为这与垃圾收集有关,而更多地与 Rico 所说的数据密度有关。假设您还有一个Vertex 类型,它包含两个Vector3s:一个Vector3 用于法线,一个Vector3 用于世界坐标。如果您创建了这些类型的类,那么拥有一个包含 100 个 Vertex 元素的数组将包括:

      • 100 * 8 字节(我相信 8 字节是内存中类的开销,4 字节用于类型标头,4 字节用于其他东西,GC 句柄?)
      • 100 * 4 字节(用于数组中指向Vertex 元素的指针)
      • 200 * 4 字节(用于从每个 Vertex 到两个 Vector3 元素的指针)
      • 200 * 8 字节(用于将 Vector3 设为类所支付的 8 字节开销)
      • 200 * 12 字节(对于每个Vector3 3 float 的实际有效负载)

      6000 字节(在 32 位系统上)。

      作为一个值类型,它只是 200 * 12 字节 = 2400 字节。在空间方面更有效,更不用说读取数组时的低级间接性了。

      但是占用大量空间并不一定会使其变慢,错误地使用值类型可能比将其变为类要慢,例如I have found out。您肯定希望尽可能通过ref 传递它们,避免复制它们,但这不适用于 all 操作,因此请测量。我想我记得在通过ref 时计算点积的速度较慢,可能是因为它通过使 IL 更大而以某种方式阻止了内联。但不要相信我的话,只是衡量。

      【讨论】:

      • +1 用于提及使用结构的根本原因:向量的值语义(对于这种 R^n 向量)
      【解决方案5】:

      在我看来,我会为此使用结构,因为它们大多数时候会分配在堆栈上,这将减少 GC 的压力,因此您的应用程序应该运行得更顺畅。

      一个提示,在结构上操作的方法可能应该将参数作为 ref 和 out 参数,这将减少在传递和返回结构时发生的数据复制量。

      【讨论】:

      • refout 'tip' 是(一般)不好的建议
      • @Henk:我不明白你为什么不鼓励refout。毕竟,类(引用类型)隐含地具有几乎相同的行为。 struct-passed-by-ref 与类一样高效,不会对 GC 造成压力。
      • @Ben - 一个可能的原因是您失去了整个“大小”优势,尤其是在 x64 上。如果类型很小且不可变,则可能仅复制它会更快。
      • Ben(和@Marc), ref 和 out 应该在情况需要时使用,而不是作为优化。您的“提示”是理所当然地建议这样做,我只会非常(非常)很少使用它,并在其中添加大量 cmets。
      • @Henk:我承认措辞可能更明确,但提示专门针对将在 3d 库中使用的这种情况。尤其是像 Matrix 这样的类型的定义在这种性质的库中是不可避免的。
      【解决方案6】:

      我们最近将一些数学类型(vec3、vec4、矩阵等)转换为使用结构而不是类,并且在基准测试中稍微快一些,它还降低了 GC 压力(我们有数百兆字节的 Vec3在相对较短的时间内分配,通过 CLR 分析器发现)。

      有趣的是,向量和矩阵的 Xna 实现是使用结构完成的。如果您也关心性能,也可以在适当的情况下通过引用传递。

      【讨论】:

      • 它们的范围从 2x2 的浮点数到 4x4 的浮点数。我们并没有真正使用更大的东西,所以虽然我相信你会用花车得到好的结果,但我不确定双打是否会被证明是一座过分的桥梁。我猜是剖析并找出答案!
      【解决方案7】:

      我猜你担心性能问题?

      如果它们是不可变的,它们应该在行为上是相同的。只用一个,如果结果证明存在性能问题,请稍后再切换。

      如果您的目标是 .NET CE,您可能应该考虑使用结构,因为 GC 的效率不如完整的 .NET 实现。

      【讨论】:

      • 以后切换可能会很困难——他正在创建一个几何库,简单地切换基本类型会破坏他库的所有使用者。最好尽早做出正确的决定。
      • “正确”可能是由他还没有足够的数据来完全了解的问题决定的。无论如何,他的库很可能会在发布之前进行一些内部工作和测试。我希望他有足够的信息在发布前做出正确的决定。
      【解决方案8】:

      结构(通常)存储在堆栈中,这可能使它们更高效,但可能不足以产生明显的差异。

      http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx?ArticleID=9adb0e3c-b3f6-40b5-98b5-413b6d348b91

      【讨论】:

      • 不,结构并不(总是)存储在堆栈中。
      • 虽然链接的文章错误地声称在方法中声明的所有值类型的变量都存储在该堆栈中,但它至少得到了这么多正确。实际上,方法内的声明可以创建局部变量(存储在堆栈中)或闭包(存储在堆中)。
      • @Henk, @Ben:所以,赞成和接受的答案与我的答案和@kyoryu 的答案基本相同......哪个被否决了?
      • 戴夫,不是我的-1,而是说“结构存储在堆栈上”是让它们在这里的可靠方法。阅读该主题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-14
      • 2019-12-01
      • 2016-10-21
      • 2016-10-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多