¿Por qué Farseer 2.x almacena temporarios como miembros y no en la pila? (.RED)

10

ACTUALIZACIÓN: Esta pregunta se refiere a Farseer 2.x. El nuevo 3.x no parece hacer esto.

Estoy usando Farseer Physics Engine bastante ampliamente en este momento, y he notado que parece almacenar muchos tipos de valores temporales como miembros de la clase, y no en la pila como cabría esperar.

Aquí hay un ejemplo de la Bodyclase:

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;
}

Parece que es una optimización de rendimiento manual. ¿Pero no veo cómo esto podría ayudar al rendimiento? (En todo caso, creo que dolería al hacer que los objetos sean mucho más grandes).

Andrew Russell
fuente

Respuestas:

6

Aunque en .NET los tipos de valor se almacenan en la pila, lo que resulta en un costo de asignación mínimo, sin embargo, no elimina el costo de inicialización.

En este caso, tenemos un conjunto de funciones que utilizan una o dos matrices temporales, lo que daría como resultado la inicialización de 16-32 flotantes por llamada. Si bien esto puede parecer insignificante, si los métodos se usan con la suficiente frecuencia (por ejemplo, miles y miles de veces por cuadro), la sobrecarga total puede tener un impacto significativo. Si dicha técnica se usa sistemáticamente en todos estos métodos, la sobrecarga eliminada puede ser considerable.

Si bien el uso de una técnica de este tipo elimina la capacidad de proporcionar seguridad de roscas a nivel de objeto, generalmente no es prudente proporcionar esa garantía a un nivel tan granular.

Jason Kozak
fuente
¿Estás seguro de eso? Uno de los pensamientos que tuve fue que podría ser para evitar llamar a los constructores. Pero para los tipos de valor no necesita llamar a un constructor, incluido el constructor sin parámetros predeterminado, si va a configurar todos los miembros (o pasarlo como outparámetro). Estoy bastante seguro de que el objetivo de esta regla es que el compilador puede omitir poner a cero esa memoria, ¿verdad? (¿Es realmente tan lento mover el puntero de la pila?)
Andrew Russell
Sorprendente, no? Desafortunadamente, si inspecciona el IL generado, las matrices temporales se inicializan. Algunas pruebas rápidas muestran que la versión member-temp es ~ 10-15% más rápida.
Jason Kozak
1
Estoy aturdido . En "Comprender el rendimiento del marco XNA" (GDC2008), Shawn Hargreaves dice acerca de las estructuras: "[el JIT] generalmente descubrirá: 'en la siguiente línea establece inmediatamente los tres campos [del Vector3], por lo que ni siquiera necesito para inicializarlo a cero '" . De ahí proviene mi información. Pero al volver a escuchar ahora, solo dice "generalmente". El siguiente punto inmediato en la presentación es que el JIT se comporta de manera diferente con el depurador adjunto, lo que afecta el rendimiento (¿cómo lo probó?). También: él está hablando del JIT aquí, así que tal vez el IL se mantenga "agradable" (¿verificabilidad?).
Andrew Russell
El IL fue inspeccionado a través de Reflector, y las pruebas se ejecutaron fuera del IDE integrado en la versión (en Windows, ya no tengo una membresía CC para probar)
Jason Kozak
1
En base a esto, me pregunto si sería mejor (y cuánto mejor sería) hacer que esos miembros temporales static(y / o reutilizarlos más agresivamente). Tal como están las cosas, por ejemplo, la Bodyclase en Farseer tiene unos 73 miembros flotantes de miembros "innecesarios".
Andrew Russell
-1

Buena pregunta. Soy un tipo bastante afilado de C # / .NET y un poco loco por el rendimiento, y esto me parece una decisión de diseño bastante extraña. Lo primero que me llama la atención es que este código no es seguro para subprocesos. No sé si eso es un problema en un sistema de Física, pero almacenar datos temporales fuera del alcance de un método suele ser una receta para el desastre.

Honestamente, si me encuentro con este tipo de código en un marco de terceros, probablemente trataría de encontrar otro marco.

Mike Strobel
fuente
3
Realmente no responde la pregunta.
Brian Ortiz
Sí, lo mejor que puedo hacer es confirmar que no está loco, y no parece haber ningún beneficio real de que se codifique de esa manera. El único descubrimiento es la verdadera intención de preguntarle al tipo que escribió el código :).
Mike Strobel
Gracias Mike. Estoy empezando a sospechar que el desarrollador original es el loco, no yo. Pero siempre ayuda comprobar;)
Andrew Russell
La seguridad de subprocesos a veces puede ser una garantía costosa, especialmente cuando se escribe una biblioteca pesada de computación FP para una plataforma que no hace uso de las instrucciones SIMD.
Jason Kozak
-1

El GC en el 360 básicamente solo realiza colecciones GEN 2, que son caras, por lo que las variables temporales que se crean y eliminan cada fotograma (como los objetos temporales) hacen que se ejecuten colecciones completas, lo que matará el rendimiento realmente rápido.

Sospecho que lo hicieron de esta manera para reutilizar ese objeto y no tenerlo recogido.

Steven Evers
fuente
1
Esto también se me ocurrió, pero los miembros temporales parecen ser tipos de valor, por lo que no se asignarían en el montón administrado de todos modos.
Mike Strobel
2
Correcto, pero solo si son miembros de la clase. Si son locales (con alcance de método), se asignarán en la pila. La pregunta es por qué no simplemente tomaron esa ruta.
Mike Strobel
1
@Blair: según MSDN ( msdn.microsoft.com/en-us/library/bb203912.aspx ), la Xbox360 usa .NET Compact Framework. Parece que la diferencia en GC está relacionada con eso, así que buscaría eso para seguir investigando sobre el asunto.
Logan Kincaid
1
@Blair: @Logan Kincaid es correcto. El recolector de basura de CF se comporta de manera diferente que el marco normal. Hay una buena charla sobre el tema en XNA Game Studio 3.0 Unleashed; sin embargo, ese libro pronto quedará desactualizado con el lanzamiento de 4.0.
Steven Evers
1
@Blair: Debido al entorno de memoria limitado de 360 ​​(y la mayoría de los dispositivos dirigidos a través de CF), se utiliza un GC Mark & ​​Sweep. Como resultado, muchas asignaciones pequeñas desencadenarán una recopilación, y el tiempo de recopilación es relativo al número de referencias. Muchos detalles aquí: download.microsoft.com/.../Mobility/…
Jason Kozak