Mengapa hasil Vector2.Normalize () berubah setelah memanggilnya 34 kali dengan input yang identik?

10

Berikut ini adalah program C # .NET Core 3.1 sederhana yang memanggil System.Numerics.Vector2.Normalize()dalam satu lingkaran (dengan input identik setiap panggilan) dan mencetak vektor yang dinormalisasi yang dihasilkan:

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

Dan ini adalah output dari menjalankan program itu di komputer saya (terpotong karena singkatnya):

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

Jadi pertanyaan saya adalah, mengapa hasil dari panggilan Vector2.Normalize(v)perubahan dari <0.9750545, -0.22196561>ke <0.97505456, -0.22196563>setelah menyebutnya 34 kali? Apakah ini yang diharapkan, atau ini bug dalam bahasa / runtime?

Walt D
sumber
Mengapung aneh
Milney
2
@Milney Mungkin, tetapi mereka juga deterministik . Perilaku ini tidak dijelaskan hanya dengan mengapung menjadi aneh.
Konrad Rudolph

Jawaban:

14

Jadi pertanyaan saya adalah, mengapa hasil pemanggilan Vector2.Normalisasi (v) berubah dari <0,9750545, -0,22196561> menjadi <0,97505456, -0,22196563> setelah memanggilnya 34 kali?

Jadi pertama - mengapa perubahan terjadi. Perubahan diamati karena kode yang menghitung perubahan nilai-nilai itu juga.

Jika kita membobol WinDbg sejak awal dalam eksekusi kode pertama dan masuk sedikit ke dalam kode yang menghitung Normalizevektor ed, kita bisa melihat perakitan berikut (lebih atau kurang - saya telah mengurangi beberapa bagian):

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]

dan setelah ~ 30 eksekusi (lebih lanjut tentang nomor ini nanti) ini akan menjadi kode:

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

Opcode yang berbeda, ekstensi yang berbeda - SSE vs AVX dan, saya kira, dengan opcode yang berbeda kita mendapatkan ketepatan perhitungan yang berbeda.

Jadi sekarang lebih banyak tentang mengapa? .NET Core (tidak yakin tentang versi - dengan asumsi 3.0 - tetapi telah diuji pada 2.1) memiliki sesuatu yang disebut "Kompilasi JIT Tiered". Apa yang dilakukannya adalah pada awalnya menghasilkan kode yang dihasilkan cepat, tetapi mungkin tidak super optimal. Hanya nanti ketika runtime mendeteksi bahwa kode tersebut sangat dimanfaatkan maka akan menghabiskan waktu tambahan untuk menghasilkan kode baru yang lebih dioptimalkan. Ini adalah hal baru di .NET Core sehingga perilaku seperti itu mungkin tidak diamati sebelumnya.

Juga mengapa 34 panggilan? Ini agak aneh karena saya berharap ini terjadi sekitar 30 eksekusi karena ini adalah ambang di mana kompilasi berjenjang dimulai. Konstanta dapat dilihat pada kode sumber coreclr . Mungkin ada beberapa variabilitas tambahan saat tendangan masuk.

Hanya untuk mengonfirmasi bahwa ini masalahnya, Anda dapat menonaktifkan kompilasi berjenjang dengan mengatur variabel lingkungan dengan mengeluarkan set COMPlus_TieredCompilation=0dan memeriksa lagi eksekusi. Efek anehnya hilang.

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>

Apakah ini yang diharapkan, atau ini bug dalam bahasa / runtime?

Sudah ada bug yang dilaporkan untuk ini - Masalah 1119

Paweł Łukasik
sumber
Mereka tidak memiliki petunjuk apa yang menyebabkannya. Semoga OP dapat menindaklanjuti dan memposting tautan ke jawaban Anda di sini.
Hans Passant
1
Terima kasih atas jawaban yang menyeluruh dan informatif! Laporan bug itu sebenarnya adalah laporan saya yang saya ajukan setelah memposting pertanyaan ini, tidak tahu apakah itu benar-benar bug atau tidak. Kedengarannya seperti mereka menganggap perubahan nilai sebagai perilaku yang tidak diinginkan yang dapat mengakibatkan heisenbug dan sesuatu yang harus diperbaiki.
Walt D
Ya, saya harus memeriksa repo sebelum melakukan analisis pada jam 2 pagi :) Pokoknya itu masalah yang menarik untuk dilihat.
Paweł Łukasik
@HansPassant Maaf, saya tidak yakin apa yang Anda sarankan saya lakukan. Bisakah Anda mengklarifikasi?
Walt D
Masalah github itu diposting oleh Anda, bukan? Biarkan mereka tahu bahwa mereka salah menebak.
Hans Passant