Jul*_*anR 32 c# performance struct raytracing struct-vs-class
我正在做一个光线跟踪器爱好项目,最初我使用的是我的Vector和Ray对象的结构,我认为光线跟踪器是使用它们的完美情况:你创造了数百万个它们,它们的寿命不长于单个方法,它们很轻巧.但是,通过简单地在Vector和Ray上将'struct'更改为'class',我获得了非常显着的性能提升.
是什么赋予了?它们都很小(Vector为3个浮点数,Ray为2个向量),不要过度复制.当然,我确实将它们传递给方法,但这是不可避免的.那么在使用结构时会导致性能下降的常见缺陷是什么?我已阅读此 MSDN文章,其中说明如下:
运行此示例时,您将看到struct循环的速度提高了几个数量级.但是,当您将ValueTypes视为对象时,请注意使用ValueTypes.这会给你的程序增加额外的装箱和拆箱开销,并且最终会比你坚持使用物品时花费更多!要查看此操作,请修改上面的代码以使用foos和bar数组.你会发现性能或多或少相等.
然而它已经很老了(2001年)而整个"把它们放在一个阵列导致拳击/拆箱"让我觉得奇怪.真的吗?但是,我确实预先计算了主光线并将它们放在一个数组中,所以我接受了这篇文章,并在我需要时计算了主光线并且从未将它们添加到数组中,但它没有改变任何东西:课程,它仍然快1.5倍.
我正在运行.NET 3.5 SP1,我相信修复了一个问题,即struct方法没有内联,所以也不可能.
所以基本上:任何提示,需要考虑的事项和避免的事项?
编辑:正如在一些答案中所建议的那样,我已经设置了一个测试项目,我尝试将结构作为参考传递.添加两个向量的方法:
public static VectorStruct Add(VectorStruct v1, VectorStruct v2)
{
return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}
public static VectorStruct Add(ref VectorStruct v1, ref VectorStruct v2)
{
return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}
public static void Add(ref VectorStruct v1, ref VectorStruct v2, out VectorStruct v3)
{
v3 = new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}
Run Code Online (Sandbox Code Playgroud)
对于每个我得到以下基准方法的变体:
VectorStruct StructTest()
{
Stopwatch sw = new Stopwatch();
sw.Start();
var v2 = new VectorStruct(0, 0, 0);
for (int i = 0; i < 100000000; i++)
{
var v0 = new VectorStruct(i, i, i);
var v1 = new VectorStruct(i, i, i);
v2 = VectorStruct.Add(ref v0, ref v1);
}
sw.Stop();
Console.WriteLine(sw.Elapsed.ToString());
return v2; // To make sure v2 doesn't get optimized away because it's unused.
}
Run Code Online (Sandbox Code Playgroud)
所有人似乎表现得非常相同.是否有可能它们被JIT优化为通过这个结构的最佳方式?
EDIT2:我必须在我的测试项目中使用结构的方式注意是不是使用的一类快了约50%.为什么这对我的光线跟踪器不同,我不知道.
ILo*_*ran 26
结构数组在内存中是一个连续的结构,而对象数组(引用类型的实例)中的项需要由指针单独寻址(即对垃圾收集堆上的对象的引用).因此,如果您同时处理大量项目,结构将为您带来性能提升,因为它们需要更少的间接性.此外,结构不能被继承,这可能允许编译器进行额外的优化(但这只是一种可能性,取决于编译器).
但是,结构具有完全不同的赋值语义,也不能继承.因此,除了在需要时给定的性能原因外,我通常会避免使用结构.
结构
由struct(值类型)编码的值数组v在内存中如下所示:
VVVV
类
由类(引用类型)编码的值数组v如下所示:
PPPP
..v..v ...... VV.
其中p是指针或引用,指向堆上的实际值v.圆点表示可能散布在堆上的其他对象.在引用类型的情况下,您需要通过相应的p引用v,在值类型的情况下,您可以通过其在数组中的偏移量直接获取值.
Guf*_*ffa 11
在何时使用结构的建议中,它表示它不应该大于16个字节.您的Vector是12个字节,接近极限.Ray有两个Vector,它是24个字节,显然超过了建议的限制.
当结构大于16个字节时,不能再使用一组指令有效地复制它,而是使用循环.因此,通过传递这个"魔法"限制,当您传递结构时,实际上比传递对象的引用要多得多.这就是为什么代码在类中更快的原因,尽管在分配对象时会有更多的开销.
Vector可能仍然是一个结构,但Ray太大而不能作为结构很好地工作.
在.NET泛型之前写的关于装箱/拆箱的任何内容都可以用一些盐.通用集合类型消除了对值类型装箱和拆箱的需要,这使得在这些情况下使用结构更有价值.
至于你的具体减速 - 我们可能需要看一些代码.
基本上,不要让它们太大,并尽可能通过ref传递它们.我以完全相同的方式发现了这一点......通过将我的Vector和Ray类更改为结构体.
随着更多内存的传递,它必然会导致缓存抖动.
我认为关键在于你帖子中的这两个陈述:
你创造了数百万
和
当然,我确实将它们传递给了方法
现在,除非您的结构大小小于或等于4个字节(如果您使用的是64位系统,则为8个字节),如果您只是传递了一个对象引用,那么您将在每个方法调用上复制更多内容.
小智 6
我要寻找的第一件事是确保你已经明确地实现了Equals和GetHashCode.如果不这样做意味着每个运行时实现都会执行一些非常昂贵的操作来比较两个结构实例(在内部它使用反射来确定每个私有字段,然后检查它们是否相等,这会导致大量的分配) .
通常,您可以做的最好的事情是在分析器下运行代码并查看慢速部件的位置.这可以是令人大开眼界的体验.
| 归档时间: |
|
| 查看次数: |
12551 次 |
| 最近记录: |