Voici un simple programme C # .NET Core 3.1 qui appelle System.Numerics.Vector2.Normalize()
en boucle (avec une entrée identique à chaque appel) et imprime le vecteur normalisé résultant:
using System;
using System.Numerics;
using System.Threading;
namespace NormalizeTest
{
class Program
{
static void Main()
{
Vector2 v = new Vector2(9.856331f, -2.2437377f);
for(int i = 0; ; i++)
{
Test(v, i);
Thread.Sleep(100);
}
}
static void Test(Vector2 v, int i)
{
v = Vector2.Normalize(v);
Console.WriteLine($"{i:0000}: {v}");
}
}
}
Et voici le résultat de l'exécution de ce programme sur mon ordinateur (tronqué par souci de concision):
0000: <0.9750545, -0.22196561>
0001: <0.9750545, -0.22196561>
0002: <0.9750545, -0.22196561>
...
0031: <0.9750545, -0.22196561>
0032: <0.9750545, -0.22196561>
0033: <0.9750545, -0.22196561>
0034: <0.97505456, -0.22196563>
0035: <0.97505456, -0.22196563>
0036: <0.97505456, -0.22196563>
...
Donc , ma question est, pourquoi le résultat de l' appel Vector2.Normalize(v)
changement de <0.9750545, -0.22196561>
à <0.97505456, -0.22196563>
après l' appeler 34 fois? Est-ce attendu ou s'agit-il d'un bogue dans la langue / le runtime?
Réponses:
Alors d'abord - pourquoi le changement se produit. La modification est observée car le code qui calcule ces valeurs change également.
Si nous entrons dans WinDbg au début des premières exécutions du code et descendons un peu dans le code qui calcule le
Normalize
vecteur ed, nous pourrions voir l'assemblage suivant (plus ou moins - j'ai coupé certaines parties):et après ~ 30 exécutions (plus sur ce nombre plus tard), ce serait le code:
Différents opcodes, différentes extensions - SSE vs AVX et, je suppose, avec différents opcodes, nous obtenons une précision différente des calculs.
Alors maintenant, plus sur le pourquoi? .NET Core (pas sûr de la version - en supposant 3.0 - mais il a été testé en 2.1) a quelque chose qui s'appelle "compilation JIT à plusieurs niveaux". Ce qu'il fait, c'est qu'au début, il produit du code qui est généré rapidement, mais qui n'est peut-être pas super optimal. Ce n'est que plus tard, lorsque le runtime détecte que le code est très utilisé, qu'il passe du temps supplémentaire à générer du nouveau code, plus optimisé. Il s'agit d'une nouveauté dans .NET Core, de sorte qu'un tel comportement pourrait ne pas être observé plus tôt.
Aussi pourquoi 34 appels? C'est un peu étrange car je m'attendrais à ce que cela se produise autour de 30 exécutions car c'est le seuil auquel la compilation à plusieurs niveaux entre en jeu. La constante peut être vue dans le code source de coreclr . Il y a peut-être une certaine variabilité supplémentaire au moment où il entre en jeu.
Juste pour confirmer que c'est le cas, vous pouvez désactiver la compilation à plusieurs niveaux en définissant la variable d'environnement en émettant
set COMPlus_TieredCompilation=0
et en vérifiant à nouveau l'exécution. L'effet étrange a disparu.Un bogue a déjà été signalé à ce sujet - Problème 1119
la source