Aquí hay un programa simple de C # .NET Core 3.1 que llama System.Numerics.Vector2.Normalize()
en un bucle (con entrada idéntica en cada llamada) e imprime el vector normalizado resultante:
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}");
}
}
}
Y aquí está el resultado de ejecutar ese programa en mi computadora (truncado por brevedad):
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>
...
Entonces mi pregunta es, ¿por qué el resultado de llamar Vector2.Normalize(v)
cambia de <0.9750545, -0.22196561>
a <0.97505456, -0.22196563>
después de llamarlo 34 veces? ¿Es esto esperado o es un error en el lenguaje / tiempo de ejecución?
Respuestas:
Primero, por qué ocurre el cambio. El cambio se observa porque el código que calcula esos valores también cambia.
Si entramos en WinDbg al principio de las primeras ejecuciones del código y profundizamos un poco en el código que calcula el
Normalize
vector ed, podríamos ver el siguiente ensamblaje (más o menos, he cortado algunas partes):y después de ~ 30 ejecuciones (más sobre este número más adelante) este sería el código:
Diferentes códigos de operación, diferentes extensiones: SSE vs AVX y, supongo, con diferentes códigos de operación obtenemos una precisión diferente de los cálculos.
¿Entonces ahora más sobre el por qué? .NET Core (no estoy seguro de la versión, suponiendo 3.0, pero se probó en 2.1) tiene algo que se llama "compilación JIT por niveles". Lo que hace es que al principio produce código que se genera rápidamente, pero que podría no ser súper óptimo. Solo más tarde, cuando el tiempo de ejecución detecte que el código es altamente utilizado, pasará un tiempo adicional para generar un código nuevo y más optimizado. Esto es algo nuevo en .NET Core, por lo que tal comportamiento podría no haberse observado antes.
¿También por qué 34 llamadas? Esto es un poco extraño, ya que esperaría que esto suceda alrededor de 30 ejecuciones, ya que este es el umbral en el que entra en juego la compilación por niveles. La constante se puede ver en el código fuente de coreclr . Tal vez hay alguna variabilidad adicional cuando se activa.
Solo para confirmar que este es el caso, puede deshabilitar la compilación escalonada configurando la variable de entorno emitiendo
set COMPlus_TieredCompilation=0
y comprobando la ejecución nuevamente. El extraño efecto se ha ido.Ya hay un error reportado para esto - Problema 1119
fuente