1

Autodesk TinkerBoxの更新で、Windows で実行される内部専用の開発バージョンと iOS の最終ターゲットで実行されるバージョンとの間で予期しない浮動小数点計算の違いに遭遇しました (次の情報はデバッグに基づいています)。ビルドは iPad で実行されます1)。

物理学のニーズにシマリスを使用します。これがこの問題の唯一の計算である可能性は決してありませんが、それは私が分析していた特定のものです:

static inline cpFloat
cpvcross(const cpVect &v1, const cpVect &v2)
{
    return v1.x*v2.y - v1.y*v2.x;
}

私が見ている特定のケースはv1、(0xC0A7BC40 [-5.241729736328125]、0xC0E84C80 [-7.25933837890625]) およびv2(0x428848FB [68.14253997802734]、0x42BCBE40 [94.37158203125]) です。値の 16 進バージョンに焦点を当てます。これらは両方のプラットフォームの入力である正確な値であり、両方のプラットフォームのメモリ位置を検査することによって検証されるからv1ですv2。参考までに、括弧内の浮動小数点値は、このサイトに 16 進値を入力する際に​​取得したものです。

Windows では結果は 0xBA15F8E8 [-0.0005720988847315311] になり、iOS では結果は 0xBA100000 [-0.00054931640625] になります。もちろん、違いは小さいですが、パーセンテージで考えると実際にはそうではなく、時間の経過とともに蓄積され、物理動作の偏差が示されます。(ダブルスの使用を提案しないでください。もちろん、ゲームの速度が低下します。ダブルスを使用しないことは問題ではありません。:))

参考までに、これは両方のプラットフォームでのデバッグ ビルドであり、コードは次のようにコンパイルされます。

Windows

static inline cpFloat
cpvcross(const cpVect &v1, const cpVect &v2)
{
01324790  push        ebp  
01324791  mov         ebp,esp 
01324793  sub         esp,0C4h 
01324799  push        ebx  
0132479A  push        esi  
0132479B  push        edi  
0132479C  lea         edi,[ebp-0C4h] 
013247A2  mov         ecx,31h 
013247A7  mov         eax,0CCCCCCCCh 
013247AC  rep stos    dword ptr es:[edi] 
    return v1.x*v2.y - v1.y*v2.x;
013247AE  mov         eax,dword ptr [v1] 
013247B1  fld         dword ptr [eax] 
013247B3  mov         ecx,dword ptr [v2] 
013247B6  fmul        dword ptr [ecx+4] 
013247B9  mov         edx,dword ptr [v1] 
013247BC  fld         dword ptr [edx+4] 
013247BF  mov         eax,dword ptr [v2] 
013247C2  fmul        dword ptr [eax] 
013247C4  fsubp       st(1),st 
013247C6  fstp        dword ptr [ebp-0C4h] 
013247CC  fld         dword ptr [ebp-0C4h] 
}
013247D2  pop         edi  
013247D3  pop         esi  
013247D4  pop         ebx  
013247D5  mov         esp,ebp 
013247D7  pop         ebp  
013247D8  ret              

iOS

invent`cpvcross at cpVect.h:63:
0x94a8:  sub    sp, sp, #8
0x94ac:  str    r0, [sp, #4]
0x94b0:  str    r1, [sp]
0x94b4:  ldr    r0, [sp, #4]
0x94b8:  vldr   s0, [r1]
0x94bc:  vldr   s1, [r1, #4]
0x94c0:  vldr   s2, [r0]
0x94c4:  vldr   s3, [r0, #4]
0x94c8:  vmul.f32 s1, s2, s1
0x94cc:  vmul.f32 s0, s3, s0
0x94d0:  vsub.f32 s0, s1, s0
0x94d4:  vmov   r0, s0
0x94d8:  add    sp, sp, #8
0x94dc:  bx     lr   

私が知る限り、各命令がオペランドの結果を同じように計算していると仮定すると、これらの計算は同じです。Xcode では、何らかの理由 (Visual Studio では許可されています) で命令ごとにステップ オーバーすることができないため、Intel FP ユニットと比較して逸脱している命令を絞り込むことはできません。

では、なぜこのような単純な計算の結果が 2 つの CPU 間でこれほど異なるのでしょうか?

4

1 に答える 1

2

計算に異なる浮動小数点精度を使用した結果が表示されています。

x86 コードでは、計算は拡張精度 (80 ビット) の FPU レジスタで行われますが、NEON コードは浮動小数点数 (32 ビット) を使用します。明らかに、乗算と減算の際の精度が高いため、x86 コードはより多くのビットを保持できますが、ARM コードはビットを失います。

_controlfp関数を使用すると、すべての計算に特定の精度を使用するよう FPU に指示できます。MSDN の例を使用して小さなプログラムを作成し、ARM コードと同じ結果を得ることができました。

#include <stdio.h>
typedef float cpFloat;
struct cpVect  {cpFloat x, y;};
struct cpVectI {unsigned int x, y;};
union cpv {cpVectI i; cpVect f;};
union cfi { float f; unsigned int i;};

cpFloat cpvcross(const cpVect &v1, const cpVect &v2)
{
    return v1.x*v2.y - v1.y*v2.x;
}

#include <float.h>
#pragma fenv_access (on)

void main(void)
{
  cpv v1, v2;
  cfi fi;
  v1.i.x = 0xC0A7BC40;
  v1.i.y = 0xC0E84C80;
  v2.i.x = 0x428848FB;
  v2.i.y = 0x42BCBE40;

  unsigned int control_word_x87;

  // Show original x87 control word and do calculation.
  __control87_2(0, 0, &control_word_x87, 0);
  printf( "Original: 0x%.4x\n", control_word_x87 );
  fi.f = cpvcross(v1.f, v2.f);
  printf("Result: %g (0x%08X)\n", fi.f, fi.i);

  // Set precision to 24 bits and recalculate.
  __control87_2(_PC_24, MCW_PC, &control_word_x87, 0);
  printf( "24-bit:   0x%.4x\n", control_word_x87);
  fi.f = cpvcross(v1.f, v2.f);
  printf("Result: %g (0x%08X)\n", fi.f, fi.i);

  // Restore default precision-control bits and recalculate.
  __control87_2( _CW_DEFAULT, MCW_PC, &control_word_x87, 0);
  printf( "Default:  0x%.4x\n", control_word_x87 );
  fi.f = cpvcross(v1.f, v2.f);
  printf("Result: %g (0x%08X)\n", fi.f, fi.i);
}

出力は次のとおりです。

Original: 0x9001f
Result: -0.000572099 (0xBA15F8E8)
24-bit:   0xa001f
Result: -0.000549316 (0xBA100000)
Default:  0x9001f
Result: -0.000572099 (0xBA15F8E8)

この関数を使用して外部ライブラリを呼び出すときは注意してください。一部のコードはデフォルト設定に依存している可能性があり、背後で変更すると壊れます。

別のオプションとして、特定の精度を使用するSSE 組み込み関数に切り替えることもできます。残念ながら、/arch:SSE2浮動小数点に SSE2 を使用していないようです (少なくとも VS2010 では)。

于 2012-08-06T17:27:31.177 に答える