为什么Farseer 2.x将临时人员存储为成员而不是堆栈中?(。净)


10

更新:此问题涉及Farseer2.x。较新的3.x似乎没有这样做。

目前,我在广泛使用Farseer Physics Engine,并且我注意到它似乎将许多临时值类型存储为类的成员,而不是像人们期望的那样存储在堆栈中。

这是来自Body该类的示例:

private Vector2 _worldPositionTemp = Vector2.Zero;

private Matrix _bodyMatrixTemp = Matrix.Identity;
private Matrix _rotationMatrixTemp = Matrix.Identity;
private Matrix _translationMatrixTemp = Matrix.Identity;

public void GetBodyMatrix(out Matrix bodyMatrix)
{
    Matrix.CreateTranslation(position.X, position.Y, 0, out _translationMatrixTemp);
    Matrix.CreateRotationZ(rotation, out _rotationMatrixTemp);
    Matrix.Multiply(ref _rotationMatrixTemp, ref _translationMatrixTemp, out bodyMatrix);
}

public Vector2 GetWorldPosition(Vector2 localPosition)
{
    GetBodyMatrix(out _bodyMatrixTemp);
    Vector2.Transform(ref localPosition, ref _bodyMatrixTemp, out _worldPositionTemp);
    return _worldPositionTemp;
}

看起来像是手动性能优化。但是我不知道这可能对性能有何帮助?(如果有的话,我认为将对象变大会造成伤害)。

Answers:


6

尽管在.NET中,值类型存储在堆栈中,从而使分配成本降至最低,但是并不能消除初始化的成本。

在这种情况下,我们有一组使用一个或两个临时矩阵的函数,这将导致每次调用初始化16-32个浮点数。尽管这似乎微不足道,但是如果足够频繁地使用这些方法(例如,每帧数千次),则总开销可能会产生有意义的影响。如果在所有此类方法中系统地使用这种技术,则消除的开销可能会很大。

尽管使用这种技术消除了在每个对象级别提供线程安全的能力,但是在这种粒度级别提供这种保证通常是不明智的。


你确定吗?我曾经想到的是可能要避免调用构造函数。但是对于值类型,如果要设置所有成员(或将其作为out参数传递),则无需调用构造函数-包括默认的无参数构造函数。我相当确定这条规则的全部要点是,以便编译器可以跳过将内存归零的步骤-对吗?(移动堆栈指针的速度真的那么慢吗?)
Andrew Russell

令人惊讶,不是吗?不幸的是,如果您检查生成的IL,则会对临时矩阵进行初始化。一些快速测试表明,成员临时版本的速度提高了约10-15%。
杰森·科扎克

1
惊呆了。Shawn Hargreaves在“了解XNA框架性能”(GDC2008)中谈到了结构:“ [JIT]通常会弄清楚:'在下一行中,他立即设置了[Vector3]的所有三个字段,所以我什至不需要初始化为零'”。这是我的信息来自哪里。但是现在再次听,他说“通常”。演示文稿中的下一个要点是,JIT在连接调试器时的行为有所不同,从而影响了性能(您如何测试?)。另外:他在这里谈论的是准时制,所以也许IL保持“不错”(可验证性?)。
安德鲁·罗素

通过Reflector对IL进行了检查,并且测试在Release内置的IDE外部运行(在Windows上,我不再具有CC成员资格进行测试)
Jason Kozak 2010年

1
基于此-我想知道让那些成员临时成员static(和/或更积极地重用它们)是否更好(更好)。实际上,例如,BodyFarseer中的类有大约73个流通量的“不必要”成员。
安德鲁·罗素

-1

好问题。我是一个非常敏锐的C#/。NET专家,并且有点性能疯了,这对我来说似乎是一个很奇怪的设计决定。首先让我惊讶的是,此代码绝不是线程安全的。我不知道这在物理系统中是否是一个问题,但是在方法范围之外存储临时数据通常会导致灾难。

老实说,如果我经常在第三方框架中遇到这种代码,我可能会尝试寻找另一个框架。


3
并没有真正回答这个问题。
Brian Ortiz 2010年

是的,我能做的最好的就是确认他没有疯,而且用这种方式编码似乎没有任何真正的好处。真正的意图唯一的发现就是问写代码的人:)。
Mike Strobel

谢谢,迈克。我开始怀疑原始开发人员是疯狂的开发人员,而不是我。但这总是有助于检查;)
安德鲁·罗素2010年

有时,提供线程安全可能是一项昂贵的保证,尤其是在为不使用SIMD指令的平台编写FP计算繁重的库时。
杰森·科扎克

-1

360上的GC基本上只执行GEN 2集合,这很昂贵,因此在每个帧中创建和删除的临时变量(如临时对象)会导致整个集合运行,这会很快导致性能下降。

我怀疑他们是通过这种方式重用该对象而不收集它的。


1
我也遇到过这种情况,但是临时成员似乎是值类型,因此无论如何都不会在托管堆上分配它们。
Mike Strobel

2
是的,但前提是他们是班级成员。如果它们是本地的(方法范围),则将它们分配在堆栈上。问题是为什么他们不只是走那条路。
Mike Strobel,2010年

1
@Blair-根据MSDN(msdn.microsoft.com/en-us/library/bb203912.aspx),Xbox360使用.NET Compact Framework。看起来GC的差异与此有关,所以我将继续对此进行进一步研究。
Logan Kincaid 2010年

1
@Blair:@Logan Kincaid是正确的。CF的垃圾收集器的行为与常规框架不同。在XNA Game Studio 3.0 Unleashed中有一个很好的关于这个主题的演讲-但是,随着4.0的发布,该书将很快过时。
史蒂文·埃弗斯

1
@Blair:由于360的内存环境有限(以及大多数通过CF定位的设备),因此使用了Mark&Sweep GC。结果,许多小的分配将触发收集,并且收集时间与引用数有关。此处有许多详细信息:download.microsoft.com
Jason Kozak,2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.