Vector2.Normalize () sonucu aynı girişlerle 34 kez çağrıldıktan sonra neden değişiyor?


10

İşte System.Numerics.Vector2.Normalize()bir döngüde (her çağrı aynı girişle) çağıran ve elde edilen normalize edilmiş vektörü bastıran basit bir C # .NET Core 3.1 programı :

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

Ve işte bu programı bilgisayarımda çalıştırmanın çıktısı (kısaca kısaltılmış):

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>
...

Sorum şu Öyleyse, neden çağrısının sonucunu yapar Vector2.Normalize(v)den değişikliği <0.9750545, -0.22196561>için <0.97505456, -0.22196563>o 34 kez çağırdıktan sonra? Bu bekleniyor mu, yoksa bu dil / çalışma zamanında bir hata mı?



2
@Milney Belki, ama onlar da belirleyicidir . Bu davranış sadece yüzen float garip olmakla açıklanmaz.
Konrad Rudolph

Yanıtlar:


14

Öyleyse sorum, Vector2.Normalize (v) çağrılmasının sonucu neden 34 kez çağırdıktan sonra <0.9750545, -0.22196561> 'den <0.97505456, -0.22196563>' e değişiyor?

Öyleyse ilk olarak - neden değişiklik meydana geliyor. Bu değerleri hesaplayan kod da değiştiği için bu değişiklik gözlemlenir.

WinDbg kodunun ilk yürütmelerinde erkenden girip Normalizeed vektörünü hesaplayan koda biraz inersek, aşağıdaki montajı görebilirdik (az çok - bazı parçaları kestim):

movss   xmm0,dword ptr [rax]
movss   xmm1,dword ptr [rax+4]
lea     rax,[rsp+40h]
movss   xmm2,dword ptr [rax]
movss   xmm3,dword ptr [rax+4]
mulss   xmm0,xmm2
mulss   xmm1,xmm3
addss   xmm0,xmm1
sqrtss  xmm0,xmm0
lea     rax,[rsp+40h]
movss   xmm1,dword ptr [rax]
movss   xmm2,dword ptr [rax+4]
xorps   xmm3,xmm3
movss   dword ptr [rsp+28h],xmm3
movss   dword ptr [rsp+2Ch],xmm3
divss   xmm1,xmm0
movss   dword ptr [rsp+28h],xmm1
divss   xmm2,xmm0
movss   dword ptr [rsp+2Ch],xmm2
mov     rax,qword ptr [rsp+28h]

ve ~ 30 yürütmeden sonra (daha sonra bu sayı hakkında daha fazla bilgi) bu kod olacaktır:

vmovsd  xmm0,qword ptr [rsp+70h]
vmovsd  qword ptr [rsp+48h],xmm0
vmovsd  xmm0,qword ptr [rsp+48h]
vmovsd  xmm1,qword ptr [rsp+48h]
vdpps   xmm0,xmm0,xmm1,0F1h
vsqrtss xmm0,xmm0,xmm0
vinsertps xmm0,xmm0,xmm0,0Eh
vshufps xmm0,xmm0,xmm0,50h
vmovsd  qword ptr [rsp+40h],xmm0
vmovsd  xmm0,qword ptr [rsp+48h]
vmovsd  xmm1,qword ptr [rsp+40h]
vdivps  xmm0,xmm0,xmm1
vpslldq xmm0,xmm0,8
vpsrldq xmm0,xmm0,8
vmovq   rcx,xmm0

Farklı opcodes, farklı uzantılar - SSE vs AVX ve sanırım, farklı opcodes ile hesaplamaların farklı hassasiyetlerini elde ederiz.

Şimdi neden hakkında daha fazla bilgi? .NET Core (sürümden emin değilim - 3.0 varsayarak - ancak 2.1'de test edildi) "Tiered JIT derlemesi" adı verilen bir şeye sahiptir. Yaptığı şey başlangıçta hızlı üretilen ancak süper optimal olmayan kod üretiyor. Ancak daha sonra çalışma zamanı, kodun çok kullanıldığını algıladığında, yeni ve daha optimize edilmiş kod oluşturmak için biraz zaman harcayacaktır. Bu, .NET Core'da yeni bir şeydir, bu nedenle bu davranış daha önce gözlemlenmeyebilir.

Ayrıca neden 34 çağrı? Bu, sıralı derlemenin başladığı eşik olduğu için 30 civarında yürütme olmasını beklediğim için biraz garip. Sabit, coreclr'ın kaynak kodunda görülebilir . Belki başladığında bazı ek değişkenlikler vardır.

Sadece durumun böyle olduğunu doğrulamak için set COMPlus_TieredCompilation=0, yürütmeyi yeniden düzenleyerek ve kontrol ederek çevresel değişkeni ayarlayarak katmanlı derlemeyi devre dışı bırakabilirsiniz . Garip etki gitti.

C:\Users\lukas\source\repos\FloatMultiple\FloatMultiple\bin\Release\netcoreapp3.1
λ FloatMultiple.exe

0000: <0,9750545  -0,22196561>
0001: <0,9750545  -0,22196561>
0002: <0,9750545  -0,22196561>
...
0032: <0,9750545  -0,22196561>
0033: <0,9750545  -0,22196561>
0034: <0,9750545  -0,22196561>
0035: <0,97505456  -0,22196563>
0036: <0,97505456  -0,22196563>
^C
C:\Users\lukas\source\repos\FloatMultiple\FloatMultiple\bin\Release\netcoreapp3.1
λ set COMPlus_TieredCompilation=0

C:\Users\lukas\source\repos\FloatMultiple\FloatMultiple\bin\Release\netcoreapp3.1
λ FloatMultiple.exe

0000: <0,97505456  -0,22196563>
0001: <0,97505456  -0,22196563>
0002: <0,97505456  -0,22196563>
...
0032: <0,97505456  -0,22196563>
0033: <0,97505456  -0,22196563>
0034: <0,97505456  -0,22196563>
0035: <0,97505456  -0,22196563>
0036: <0,97505456  -0,22196563>

Bu bekleniyor mu, yoksa bu dil / çalışma zamanında bir hata mı?

Bunun için zaten bir hata bildirildi - Sayı 1119


Buna neden olan bir ipucu yok. Umarım OP burada cevabınızı takip edebilir ve link gönderebilir.
Hans Passant

1
Kapsamlı ve bilgilendirici cevap için teşekkürler! Bu hata raporu aslında, gerçekten bir hata olup olmadığını bilmeden, bu soruyu gönderdikten sonra dosyaladığım raporum. Değişen değeri, heisenbug'lara ve düzeltilmesi gereken bir şeyle sonuçlanabilecek istenmeyen davranışlar olarak görüyorlar.
Walt D

Evet, 2 AM'de analiz yapmadan önce repoyu kontrol etmeliydim :) Her neyse, içine bakmak ilginç bir problemdi.
Paweł Łukasik

@HansPassant Üzgünüm, ne yapmamı önerdiğinden emin değilim. Lütfen açıklar mısın?
Walt D

Bu github sorunu sizin tarafınızdan gönderildi, değil mi? Sadece yanlış tahmin ettiklerini söyleyin.
Hans Passant
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.