同一の入力で34回呼び出した後、Vector2.Normalize()の結果が変化するのはなぜですか?


10

以下は、System.Numerics.Vector2.Normalize()ループ内で(呼び出しごとに同じ入力で)呼び出し、結果の正規化されたベクトルを出力する単純なC#.NET Core 3.1プログラムです。

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

だから私の質問は、なぜ呼び出しを行った結果が34回呼び出した後にVector2.Normalize(v)から<0.9750545, -0.22196561>に変わるの<0.97505456, -0.22196563>ですか?これは予期されたものですか、それとも言語/ランタイムのバグですか?


フロートがおかしい
ミルニー

2
@ミルニーかもしれませんが、彼らは決定論的でもあります。この動作は、フロートが奇妙なだけで説明されるわけではありません。
Konrad Rudolph

回答:


14

だから私の質問は、Vector2.Normalize(v)を呼び出した結果が、34回呼び出した後、<0.9750545、-0.22196561>から<0.97505456、-0.22196563>に変化するのはなぜですか?

それではまず、変更が発生する理由を説明します。これらの値を計算するコードも変更されるため、変更が観察されます。

コードの最初の実行の早い段階でWinDbgに侵入し、Normalizeedベクトルを計算するコードに少し入ると、次のアセンブリが表示されます(多かれ少なかれ、一部を切り詰めました)。

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

さまざまなオペコード、さまざまな拡張子-SSEとAVX、そしておそらく、さまざまなオペコードを使用すると、計算の精度が異なります。

それでは、その理由について詳しく説明しましょう。.NET Core(バージョンについては不明-3.0を想定-2.1でテスト済み)には、「Tiered 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がフォローアップして、ここに回答へのリンクを投稿できます。
Hans Passant

1
徹底的で有益な答えをありがとう!そのバグレポートは、実際にこの質問を投稿した後に提出した私のレポートであり、本当にバグかどうかはわかりません。彼らが変更する値を、ハイゼンバグや修正すべき何かを引き起こす可能性のある望ましくない動作であると考えているように聞こえます。
Walt D

ええ、午前2時に分析を行う前にレポを確認する必要がありました:)とにかく調べるのは興味深い問題でした。
パヴェルŁukasik

@HansPassant申し訳ありませんが、私が何を提案しているのかわかりません。明確にしていただけますか?
Walt D

そのgithubの問題はあなたが投稿したものですね。彼らが間違っていると思ったことを彼らに知らせてください。
Hans Passant
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.