Pourquoi Farseer 2.x stocke-t-il les temporaires en tant que membres et non sur la pile? (.NET)

10

MISE À JOUR: Cette question se réfère à Farseer 2.x. Le nouveau 3.x ne semble pas faire cela.

J'utilise Farseer Physics Engine pour le moment, et j'ai remarqué qu'il semble stocker beaucoup de types de valeurs temporaires en tant que membres de la classe, et non sur la pile comme on pourrait s'y attendre.

Voici un exemple de la Bodyclasse:

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

Cela ressemble à une optimisation manuelle des performances. Mais je ne vois pas comment cela pourrait éventuellement améliorer les performances? (Si quoi que ce soit, je pense que cela ferait mal en rendant les objets beaucoup plus gros).

Andrew Russell
la source

Réponses:

6

Bien que les types de valeurs .NET soient stockés sur la pile, ce qui entraîne un coût d'allocation minimal, cela n'élimine cependant pas le coût d'initialisation.

Dans ce cas, nous avons un ensemble de fonctions utilisant une ou deux matrices temporaires, ce qui entraînerait l'initialisation de 16 à 32 flottants par appel. Bien que cela puisse sembler insignifiant, si les méthodes sont utilisées assez souvent (par exemple, des milliers et des milliers de fois par trame), la surcharge totale peut avoir un impact significatif. Si une telle technique est utilisée systématiquement dans toutes ces méthodes, les frais généraux éliminés peuvent être considérables.

Bien que l'utilisation d'une telle technique élimine la possibilité d'assurer la sécurité des threads au niveau de chaque objet, il n'est généralement pas judicieux de fournir cette garantie à un niveau aussi granulaire.

Jason Kozak
la source
Êtes-vous sûr de cela? J'ai pensé que c'était peut-être pour éviter d'appeler des constructeurs. Mais pour les types de valeur, vous n'avez pas besoin d'appeler un constructeur - y compris le constructeur sans paramètre par défaut - si vous allez définir tous les membres (ou le passer en tant que outparamètre). Je suis assez sûr que le but de cette règle est que le compilateur puisse ignorer la mise à zéro de la mémoire - non? (Est-ce vraiment si lent de déplacer le pointeur de pile?)
Andrew Russell
Surprenant, non? Malheureusement, si vous inspectez l'IL généré, les matrices temporaires sont initialisées. Quelques tests rapides montrent que la version membre-temp est ~ 10-15% plus rapide.
Jason Kozak
1
Je suis stupéfait . Dans "Comprendre les performances du framework XNA" (GDC2008), Shawn Hargreaves dit à propos des structures: "[le JIT] comprend généralement: 'dans la ligne suivante, il définit immédiatement les trois champs [du Vector3], donc je n'ai même pas besoin pour l'initialiser à zéro "" . C'est de là que viennent mes informations. Mais en réécoutant maintenant, il dit seulement "habituellement". Le point suivant immédiat de la présentation est que le JIT se comporte différemment avec le débogueur attaché, affectant les performances (comment avez-vous testé?). Aussi: il parle du JIT ici, alors peut-être que l'IL reste "sympa" (vérifiabilité?).
Andrew Russell
L'IL a été inspecté via Reflector, et les tests ont été exécutés en dehors de l'EDI intégré dans la version (sous Windows, je n'ai plus d'adhésion CC pour tester)
Jason Kozak
1
Sur cette base - je me demande s'il serait préférable (et combien il serait préférable) de rendre ces membres temporaires static(et / ou de les réutiliser de manière plus agressive). Dans l'état actuel des choses, juste par exemple, la Bodyclasse de Farseer compte quelque 73 chars de membres "inutiles".
Andrew Russell
-1

Bonne question. Je suis un gars C # /. NET assez pointu et un peu fou de la performance, et cela me semble une décision de conception plutôt étrange. La première chose qui me saute aux yeux est que ce code n'est en aucun cas sûr pour les threads. Je ne sais pas si c'est un problème dans un système de physique, mais le stockage de données temporaires en dehors du champ d'application d'une méthode est souvent une recette pour un désastre.

Honnêtement, si je rencontrais régulièrement ce type de code dans un framework tiers, j'essaierais probablement de trouver un autre framework.

Mike Strobel
la source
3
Ne répond pas vraiment à la question.
Brian Ortiz
Ouais, le mieux que je puisse faire est de confirmer qu'il n'est pas fou, et il ne semble pas y avoir de réel avantage à le coder de cette façon. La seule chose à découvrir est de demander au gars qui a écrit le code :).
Mike Strobel
Merci, Mike. Je commence à soupçonner que le développeur d'origine est le fou, pas moi. Mais ça aide toujours de vérifier;)
Andrew Russell
La sécurité des threads peut parfois être une garantie coûteuse à fournir, en particulier lors de l'écriture d'une bibliothèque lourde de calcul FP pour une plate-forme qui n'utilise pas les instructions SIMD.
Jason Kozak
-1

Le GC sur le 360 ​​ne fait que des collections GEN 2, qui sont chères, donc les variables temporaires qui sont créées et supprimées à chaque image (comme les objets temporaires) entraînent l'exécution de toutes les collections, ce qui réduira les performances très rapidement.

Je soupçonne qu'ils l'ont fait de cette façon pour réutiliser cet objet et ne pas le récupérer.

Steven Evers
la source
1
Cela m'est également venu à l'esprit, mais les membres temporaires semblent être des types de valeur, de sorte qu'ils ne seraient de toute façon pas alloués sur le tas géré.
Mike Strobel
2
D'accord, mais seulement s'ils sont membres de la classe. S'ils sont locaux (à portée de méthode), ils seraient alloués sur la pile. La question est de savoir pourquoi ils n'ont pas simplement emprunté cette voie.
Mike Strobel
1
@Blair - Selon MSDN ( msdn.microsoft.com/en-us/library/bb203912.aspx ), la Xbox360 utilise le .NET Compact Framework. Il semble que la différence dans GC soit liée à cela, alors je poursuivrais cela pour de plus amples recherches sur la question.
Logan Kincaid
1
@Blair: @Logan Kincaid a raison. Le garbage collector des FC se comporte différemment du cadre standard. Il y a une bonne discussion sur le sujet dans XNA Game Studio 3.0 Unleashed - mais ce livre sera bientôt obsolète avec la sortie de 4.0.
Steven Evers
1
@Blair: En raison de l'environnement de mémoire limité du 360 (et de la plupart des appareils ciblés via les CF), un GC Mark & ​​Sweep est utilisé. Par conséquent, de nombreuses petites allocations déclencheront une collecte et le temps de collecte est relatif au nombre de références. Beaucoup de détails ici: download.microsoft.com/.../Mobility/…
Jason Kozak