ทำไมผลลัพธ์ของ Vector2.Normalize () จึงเปลี่ยนไปหลังจากที่เรียกมันว่า 34 ครั้งด้วยอินพุตที่เหมือนกัน?


10

นี่คือโปรแกรม 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 ครั้ง สิ่งนี้คาดหวังหรือเป็นข้อบกพร่องในภาษา / รันไทม์หรือไม่?


ลอยตัวแปลก ๆ
Milney

2
@Milney บางที แต่พวกเขาก็กำหนดไว้เช่นกัน พฤติกรรมนี้ไม่ได้อธิบายเพียงอย่างเดียวโดยการลอยตัวแปลก ๆ
Konrad Rudolph

คำตอบ:


14

ดังนั้นคำถามของฉันคือทำไมผลลัพธ์ของการเรียก Vector2. Normal (v) เปลี่ยนจาก <0.9750545, -0.22196561> เป็น <0.97505456, -0.22196563> หลังจากเรียกมัน 34 ครั้ง?

ดังนั้นก่อน - ทำไมการเปลี่ยนแปลงเกิดขึ้น มีการสังเกตการเปลี่ยนแปลงเนื่องจากรหัสที่คำนวณค่าเหล่านั้นก็เปลี่ยนแปลงเช่นกัน

ถ้าเราบุกเข้าไปใน WinDbg ก่อนในการประหารชีวิตครั้งแรกของรหัสและลงไปเล็กน้อยในรหัสที่คำนวณNormalizeเวกเตอร์ ed เราจะได้เห็นการชุมนุมต่อไปนี้ (มากหรือน้อย - ฉันได้ลดบางส่วน):

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]

และหลังจากการประหารชีวิตประมาณ 30 ครั้ง (เพิ่มเติมเกี่ยวกับหมายเลขนี้ในภายหลัง) นี่จะเป็นรหัส:

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

opcodes ที่แตกต่างกัน, ส่วนขยายที่แตกต่างกัน - SSE vs AVX และ, ฉันเดา, ด้วย opcodes ที่แตกต่างกันเราได้รับความแม่นยำที่แตกต่างกันของการคำนวณ

ดังนั้นเพิ่มเติมเกี่ยวกับสาเหตุ .NET Core (ไม่แน่ใจเกี่ยวกับเวอร์ชัน - สมมติว่า 3.0 - แต่ได้รับการทดสอบใน 2.1) มีบางสิ่งที่เรียกว่า "การรวบรวม JIT แบบขั้น" สิ่งที่ทำอยู่ในตอนเริ่มต้นนั้นจะสร้างโค้ดที่สร้างขึ้นอย่างรวดเร็ว แต่อาจไม่เหมาะสมที่สุด ในภายหลังเมื่อรันไทม์ตรวจพบว่ามีการใช้รหัสอย่างมากจะใช้เวลาเพิ่มเติมในการสร้างรหัสใหม่ที่ปรับให้เหมาะสมยิ่งขึ้น นี่เป็นสิ่งใหม่ใน. NET Core ดังนั้นพฤติกรรมดังกล่าวอาจไม่ได้รับการสังเกตก่อนหน้านี้

ทำไมต้องโทร 34 ครั้ง? นี่เป็นเรื่องแปลกที่ฉันคาดว่าสิ่งนี้จะเกิดขึ้นประมาณ 30 การประหารชีวิตเพราะนี่คือเกณฑ์ที่การสะสมเทียร์จะเริ่มขึ้นค่าคงที่สามารถเห็นได้ในซอร์สโค้ดของcoreclr อาจจะมีความแปรปรวนเพิ่มเติมเมื่อเตะเข้า

เพียงเพื่อยืนยันว่าเป็นกรณีนี้คุณสามารถปิดใช้งานการรวบรวมแบบเทียร์โดยการตั้งค่าตัวแปรสภาพแวดล้อมโดยการออกset COMPlus_TieredCompilation=0และตรวจสอบการดำเนินการอีกครั้ง เอฟเฟกต์แปลก ๆ หายไป

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>

สิ่งนี้คาดหวังหรือเป็นข้อบกพร่องในภาษา / รันไทม์หรือไม่?

มีข้อบกพร่องที่รายงานไว้สำหรับเรื่องนี้ - ฉบับที่ 1119


พวกเขาไม่มีเงื่อนงำสิ่งที่ทำให้เกิด หวังว่า OP สามารถติดตามและโพสต์ลิงก์ไปยังคำตอบของคุณได้ที่นี่
ฮันส์

1
ขอบคุณสำหรับคำตอบที่ละเอียดและให้ข้อมูล! รายงานข้อผิดพลาดนั้นเป็นรายงานของฉันที่ฉันยื่นหลังจากโพสต์คำถามนี้โดยไม่รู้ว่ามันเป็นข้อผิดพลาดจริงหรือไม่ ฟังดูเหมือนพวกเขาคิดว่าการเปลี่ยนค่าเป็นพฤติกรรมที่ไม่พึงประสงค์ซึ่งอาจส่งผลให้เกิดปัญหาใหญ่และสิ่งที่ควรได้รับการแก้ไข
Walt D

ใช่ฉันควรจะตรวจสอบ repo ก่อนที่จะทำการวิเคราะห์เวลา 2:00 น. :) มันเป็นปัญหาที่น่าสนใจที่จะพิจารณา
PawełŁukasik

@HansPassant ขออภัยฉันไม่แน่ใจว่าสิ่งที่คุณแนะนำฉันทำ คุณช่วยอธิบายได้ไหม?
Walt D

คุณโพสต์ปัญหา GitHub นั้นใช่มั้ย เพียงแค่ให้พวกเขารู้ว่าพวกเขาเดาผิด
Hans Passant
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.