Tại sao kết quả của Vector2.N normalize () thay đổi sau khi gọi nó 34 lần với các đầu vào giống hệt nhau?


10

Đây là một chương trình C # .NET Core 3.1 đơn giản gọi System.Numerics.Vector2.Normalize()trong một vòng lặp (với đầu vào giống hệt nhau cho mỗi cuộc gọi) và in ra kết quả vector chuẩn hóa:

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

Và đây là đầu ra của việc chạy chương trình đó trên máy tính của tôi (bị cắt ngắn để ngắn gọn):

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

Vì vậy, câu hỏi của tôi là, tại sao kết quả của gọi Vector2.Normalize(v)sự thay đổi từ <0.9750545, -0.22196561>để <0.97505456, -0.22196563>sau khi gọi đó là 34 lần? Đây có phải là dự kiến, hoặc đây là một lỗi trong ngôn ngữ / thời gian chạy?


Phao rất kỳ lạ
Milney

2
@Milney Có thể, nhưng họ cũng xác định . Hành vi này không được giải thích chỉ bởi phao là lạ.
Konrad Rudolph

Câu trả lời:


14

Vì vậy, câu hỏi của tôi là, tại sao kết quả của việc gọi Vector2. Chuẩn hóa (v) thay đổi từ <0.9750545, -0.22196561> thành <0.97505456, -0.22196563> sau khi gọi nó 34 lần?

Vì vậy, đầu tiên - tại sao sự thay đổi xảy ra. Sự thay đổi được quan sát bởi vì mã tính toán các giá trị đó cũng thay đổi.

Nếu chúng ta đột nhập vào WinDbg sớm trong các lần thực thi mã đầu tiên và đi sâu hơn một chút vào mã tính toán Normalizevectơ ed, chúng ta có thể thấy tập hợp sau (ít nhiều - tôi đã cắt giảm một số phần):

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]

và sau ~ 30 lần thực thi (sẽ nói thêm về số này sau) đây sẽ là mã:

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

Các mã khác nhau, các phần mở rộng khác nhau - SSE so với AVX và, tôi đoán, với các mã khác nhau, chúng tôi có được độ chính xác khác nhau của các phép tính.

Vì vậy, bây giờ nhiều hơn về lý do tại sao? .NET Core (không chắc chắn về phiên bản - giả sử 3.0 - nhưng nó đã được thử nghiệm trong 2.1) có một thứ gọi là "Biên dịch JIT theo tầng". Những gì nó làm là lúc đầu nó tạo ra mã được tạo ra nhanh, nhưng có thể không phải là siêu tối ưu. Chỉ sau này khi bộ thực thi phát hiện ra rằng mã được sử dụng nhiều, nó sẽ dành thêm thời gian để tạo mã mới, tối ưu hơn. Đây là một điều mới trong .NET Core vì vậy hành vi như vậy có thể không được quan sát trước đó.

Ngoài ra tại sao 34 cuộc gọi? Điều này hơi lạ vì tôi dự đoán điều này sẽ xảy ra trong khoảng 30 lần thực thi vì đây là ngưỡng mà quá trình biên dịch theo tầng bắt đầu. Có thể thấy hằng số trong mã nguồn của coreclr . Có thể có một số thay đổi bổ sung khi nó khởi động.

Chỉ cần xác nhận rằng đây là trường hợp, bạn có thể vô hiệu hóa trình biên dịch theo tầng bằng cách đặt biến môi trường bằng cách phát hành set COMPlus_TieredCompilation=0và kiểm tra lại việc thực hiện. Hiệu ứng lạ đã biến mất.

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>

Đây có phải là dự kiến, hoặc đây là một lỗi trong ngôn ngữ / thời gian chạy?

Đã có một lỗi được báo cáo cho vấn đề này - Số 1119


Họ không có manh mối gì gây ra nó. Hy vọng rằng OP có thể theo dõi và gửi một liên kết đến câu trả lời của bạn ở đây.
Hans Passant

1
Cảm ơn câu trả lời thấu đáo và nhiều thông tin! Báo cáo lỗi đó thực sự là báo cáo của tôi mà tôi đã nộp sau khi đăng câu hỏi này, không biết liệu nó có thực sự là một lỗi hay không. Âm thanh như họ coi giá trị thay đổi là hành vi không mong muốn có thể dẫn đến heisenbugs và một cái gì đó cần được sửa chữa.
Walt D

Vâng, tôi nên kiểm tra repo trước khi thực hiện phân tích lúc 2 giờ sáng :) Dù sao đó cũng là một vấn đề thú vị để xem xét.
Paweł ukasik

@HansPassant Xin lỗi, tôi không chắc những gì bạn đề nghị tôi làm. Bạn có thể vui lòng làm rõ?
Walt D

Vấn đề github đó đã được đăng bởi bạn, phải không? Chỉ cần cho họ biết rằng họ đoán sai.
Hans Passant
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.