Вот простая программа на C # .NET Core 3.1, которая вызывает System.Numerics.Vector2.Normalize()
цикл (с одинаковым вводом при каждом вызове) и распечатывает результирующий нормализованный вектор:
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}");
}
}
}
И вот результат запуска этой программы на моем компьютере (сокращенно для краткости):
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>
...
Итак, мой вопрос: почему результат вызова Vector2.Normalize(v)
меняется с <0.9750545, -0.22196561>
на <0.97505456, -0.22196563>
после вызова 34 раза? Это ожидается, или это ошибка в языке / времени выполнения?
Ответы:
Итак, во-первых, почему происходит изменение. Изменения наблюдаются, потому что код, который вычисляет эти значения, тоже изменяется.
Если мы начнем с WinDbg в начале выполнения первого кода и немного углубимся в код, который вычисляет
Normalize
вектор ed, мы можем увидеть следующую сборку (более или менее - я сократил некоторые части):и после ~ 30 казней (подробнее об этом номере позже) это будет код:
Разные коды операций, разные расширения - SSE vs AVX и, я думаю, с разными кодами операций мы получаем разную точность вычислений.
Так что теперь больше о том, почему? .NET Core (не уверен насчет версии - предполагается, что 3.0 - но она была протестирована в 2.1) имеет то, что называется «многоуровневая компиляция JIT». В начале он создает код, который генерируется быстро, но может быть неоптимальным. Только позже, когда среда выполнения обнаружит, что код интенсивно используется, она потратит дополнительное время на генерацию нового, более оптимизированного кода. Это новая вещь в .NET Core, поэтому такого поведения раньше не наблюдалось.
И почему 34 звонка? Это немного странно, так как я ожидаю, что это произойдет примерно при 30 выполнениях, поскольку это порог, с которого начинается многоуровневая компиляция. Константу можно увидеть в исходном коде coreclr . Возможно есть некоторая дополнительная изменчивость к тому, когда это начинает.
Просто чтобы подтвердить, что это так, вы можете отключить многоуровневую компиляцию, установив переменную среды, выполнив
set COMPlus_TieredCompilation=0
и снова проверив выполнение. Странный эффект исчез.Об этом уже сообщается об ошибке - выпуск 1119
источник