我正在进行一个兴趣项目——光线追踪器,最初我在我的矢量和射线对象中使用结构体,并且认为光线追踪器是使用它们的完美场景:你创建了数百万个它们,它们的生命周期不超过单个方法,它们很轻便。然而,仅仅将Vector和Ray上的'struct'更改为'class',我得到了非常显著的性能提升。
怎么回事?它们都很小(3个浮点数用于Vector,2个Vector用于Ray),不会被过度复制。当然,我需要在需要时将它们传递给方法,但这是不可避免的。那么,在使用结构体时会导致性能下降的常见陷阱是什么?我读过thisMSDN文章,其中说:
“运行此示例时,您会发现结构体循环速度要快几个数量级。但是,当您将ValueTypes像对象一样处理时,要注意不要这样做。这会给您的程序添加额外的装箱和取消装箱开销,并最终比坚持使用对象更费钱!要查看此操作,请修改上面的代码以使用foo和bar的数组。您会发现性能或多或少相等。”
但是,这篇文章已经相当老了(2001年),而整个“将它们放入数组中会导致装箱/取消装箱”让我感到奇怪。这是真的吗?然而,我确实预先计算了主光线并将其放入了一个数组中,所以我参考了这篇文章,当我需要时计算主光线并从未将其添加到数组中,但是它没有改变任何事情:使用类仍然比结构体快1.5倍。
我正在运行.NET 3.5 SP1,我相信该版本修复了结构体方法从未内联的问题,因此也不可能是这个问题。
所以基本上:有什么提示、需要考虑的事情和要避免的?
编辑:如一些答案中建议的那样,我设置了一个测试项目,在其中尝试将结构体作为ref传递。添加两个向量的方法:
怎么回事?它们都很小(3个浮点数用于Vector,2个Vector用于Ray),不会被过度复制。当然,我需要在需要时将它们传递给方法,但这是不可避免的。那么,在使用结构体时会导致性能下降的常见陷阱是什么?我读过thisMSDN文章,其中说:
“运行此示例时,您会发现结构体循环速度要快几个数量级。但是,当您将ValueTypes像对象一样处理时,要注意不要这样做。这会给您的程序添加额外的装箱和取消装箱开销,并最终比坚持使用对象更费钱!要查看此操作,请修改上面的代码以使用foo和bar的数组。您会发现性能或多或少相等。”
但是,这篇文章已经相当老了(2001年),而整个“将它们放入数组中会导致装箱/取消装箱”让我感到奇怪。这是真的吗?然而,我确实预先计算了主光线并将其放入了一个数组中,所以我参考了这篇文章,当我需要时计算主光线并从未将其添加到数组中,但是它没有改变任何事情:使用类仍然比结构体快1.5倍。
我正在运行.NET 3.5 SP1,我相信该版本修复了结构体方法从未内联的问题,因此也不可能是这个问题。
所以基本上:有什么提示、需要考虑的事情和要避免的?
编辑:如一些答案中建议的那样,我设置了一个测试项目,在其中尝试将结构体作为ref传递。添加两个向量的方法:
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);
}
对于每个 I,我都得到了以下基准测试方法的一个变化:
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.
}
所有的表现都十分相似。是否可能它们被JIT优化为传递此结构的最佳方式?
编辑2:顺便说一下,在我的测试项目中使用结构体比使用类快大约50%。我不知道这在我的光线追踪器中为什么会有所不同。