17

私は現在、C++ でベクトル数学クラスのようないくつかの glsl を作成しており、次のようなabs()関数を実装しました。

template<class T>
static inline T abs(T _a)
{
    return _a < 0 ? -_a : _a;
}

次のように、その速度をデフォルトの C++ と比較しましabsmath.h

clock_t begin = clock();
for(int i=0; i<10000000; ++i)
{
    float a = abs(-1.25);
};

clock_t end = clock();
unsigned long time1 = (unsigned long)((float)(end-begin) / ((float)CLOCKS_PER_SEC/1000.0));

begin = clock();
for(int i=0; i<10000000; ++i)
{
    float a  = myMath::abs(-1.25);
};
end = clock();
unsigned long time2 = (unsigned long)((float)(end-begin) / ((float)CLOCKS_PER_SEC/1000.0));

std::cout<<time1<<std::endl;
std::cout<<time2<<std::endl;

現在、デフォルトの abs は約 25 ミリ秒かかりますが、私の場合は 60 ミリ秒かかります。低レベルの最適化が行われていると思います。math.h abs内部でどのように機能するか知っている人はいますか?パフォーマンスの違いは劇的ではありませんが、私はただ興味があります!

4

8 に答える 8

20

それらは実装であるため、必要な数の仮定を自由に行うことができます。彼らは のフォーマットを知っておりdouble、代わりにそれを使ってトリックをすることができます。

おそらく(ほとんど質問でさえないように)、あなたdoublebinary64 format です。これは、符号に独自のビットがあり、絶対値は単にそのビットをクリアしていることを意味します。たとえば、特殊化として、コンパイラの実装者は次のことを行う場合があります

template <>
double abs<double>(const double x)
{
    // breaks strict aliasing, but compiler writer knows this behavior for the platform
    uint64_t i = reinterpret_cast<const std::uint64_t&>(x);
    i &= 0x7FFFFFFFFFFFFFFFULL; // clear sign bit

    return reinterpret_cast<const double&>(i);
}

これにより分岐がなくなり、実行速度が向上する可能性があります。

于 2010-05-20T21:48:53.907 に答える
10

2の補数の符号付き数値の絶対値を計算するためのよく知られたトリックがあります。数値が負の場合は、すべてのビットを反転して1を加算します。つまり、xorと-1を加算し、-1を減算します。正の場合は、何もしません。つまり、xorを0にして、0を引きます。

int my_abs(int x)
{
    int s = x >> 31;
    return (x ^ s) - s;
}
于 2010-05-20T22:04:35.960 に答える
9

コンパイラと設定は何ですか? MS と GCC は、多くの数学演算と文字列演算に「組み込み関数」を実装していると確信しています。

次の行:

printf("%.3f", abs(1.25));

次の "fabs" コード パス (msvcr90d.dll 内) に分類されます。

004113DE  sub         esp,8 
004113E1  fld         qword ptr [__real@3ff4000000000000 (415748h)] 
004113E7  fstp        qword ptr [esp] 
004113EA  call        abs (4110FFh) 

abs は、MSVCR90D (かなり大きい) で C ランタイムの「fabs」実装を呼び出します。

102F5730  mov         edi,edi 
102F5732  push        ebp  
102F5733  mov         ebp,esp 
102F5735  sub         esp,14h 
102F5738  fldz             
102F573A  fstp        qword ptr [result] 
102F573D  push        0FFFFh 
102F5742  push        133Fh 
102F5747  call        _ctrlfp (102F6140h) 
102F574C  add         esp,8 
102F574F  mov         dword ptr [savedcw],eax 
102F5752  movzx       eax,word ptr [ebp+0Eh] 
102F5756  and         eax,7FF0h 
102F575B  cmp         eax,7FF0h 
102F5760  jne         fabs+0D2h (102F5802h) 
102F5766  sub         esp,8 
102F5769  fld         qword ptr [x] 
102F576C  fstp        qword ptr [esp] 
102F576F  call        _sptype (102F9710h) 
102F5774  add         esp,8 
102F5777  mov         dword ptr [ebp-14h],eax 
102F577A  cmp         dword ptr [ebp-14h],1 
102F577E  je          fabs+5Eh (102F578Eh) 
102F5780  cmp         dword ptr [ebp-14h],2 
102F5784  je          fabs+77h (102F57A7h) 
102F5786  cmp         dword ptr [ebp-14h],3 
102F578A  je          fabs+8Fh (102F57BFh) 
102F578C  jmp         fabs+0A8h (102F57D8h) 
102F578E  push        0FFFFh 
102F5793  mov         ecx,dword ptr [savedcw] 
102F5796  push        ecx  
102F5797  call        _ctrlfp (102F6140h) 
102F579C  add         esp,8 
102F579F  fld         qword ptr [x] 
102F57A2  jmp         fabs+0F8h (102F5828h) 
102F57A7  push        0FFFFh 
102F57AC  mov         edx,dword ptr [savedcw] 
102F57AF  push        edx  
102F57B0  call        _ctrlfp (102F6140h) 
102F57B5  add         esp,8 
102F57B8  fld         qword ptr [x] 
102F57BB  fchs             
102F57BD  jmp         fabs+0F8h (102F5828h) 
102F57BF  mov         eax,dword ptr [savedcw] 
102F57C2  push        eax  
102F57C3  sub         esp,8 
102F57C6  fld         qword ptr [x] 
102F57C9  fstp        qword ptr [esp] 
102F57CC  push        15h  
102F57CE  call        _handle_qnan1 (102F98C0h) 
102F57D3  add         esp,10h 
102F57D6  jmp         fabs+0F8h (102F5828h) 
102F57D8  mov         ecx,dword ptr [savedcw] 
102F57DB  push        ecx  
102F57DC  fld         qword ptr [x] 
102F57DF  fadd        qword ptr [__real@3ff0000000000000 (1022CF68h)] 
102F57E5  sub         esp,8 
102F57E8  fstp        qword ptr [esp] 
102F57EB  sub         esp,8 
102F57EE  fld         qword ptr [x] 
102F57F1  fstp        qword ptr [esp] 
102F57F4  push        15h  
102F57F6  push        8    
102F57F8  call        _except1 (102F99B0h) 
102F57FD  add         esp,1Ch 
102F5800  jmp         fabs+0F8h (102F5828h) 
102F5802  mov         edx,dword ptr [ebp+0Ch] 
102F5805  and         edx,7FFFFFFFh 
102F580B  mov         dword ptr [ebp-0Ch],edx 
102F580E  mov         eax,dword ptr [x] 
102F5811  mov         dword ptr [result],eax 
102F5814  push        0FFFFh 
102F5819  mov         ecx,dword ptr [savedcw] 
102F581C  push        ecx  
102F581D  call        _ctrlfp (102F6140h) 
102F5822  add         esp,8 
102F5825  fld         qword ptr [result] 
102F5828  mov         esp,ebp 
102F582A  pop         ebp  
102F582B  ret   

リリース モードでは、代わりに FPU FABS 命令が使用されます (FPU >= Pentium でのみ 1 クロック サイクルかかります)。逆アセンブリの出力は次のとおりです。

00401006  fld         qword ptr [__real@3ff4000000000000 (402100h)] 
0040100C  sub         esp,8 
0040100F  fabs             
00401011  fstp        qword ptr [esp] 
00401014  push        offset string "%.3f" (4020F4h) 
00401019  call        dword ptr [__imp__printf (4020A0h)] 
于 2010-05-20T22:28:45.883 に答える
4

おそらく、ビットマスクを使用して符号ビットを 0 に設定しているだけです。

于 2010-05-20T21:46:46.610 に答える
3

ご使用のバージョンのabsはインライン化されており、一度計算できます。コンパイラーは、返される値が変更されないことを簡単に知ることができるため、関数を呼び出す必要さえありません。

生成されたアセンブリコードを実際に確認する必要があります(ブレークポイントを設定し、「大きな」デバッガービューを開きます。この逆アセンブリは、メモリが機能する場合は左下にあります)。その後、何が起こっているかを確認できます。

プロセッサに関するドキュメントは、それほど問題なくオンラインで見つけることができます。すべての手順が説明されているので、何が起こっているのかを理解できます。または、ここに貼り付けてください。;)

于 2010-05-20T22:11:22.730 に答える
3

いくつかのことが考えられます:

  • 最初の呼び出しで を使用してもstd::absよろしいですか? absCの整数を使用することもできます(std::abs明示的に呼び出すか、または持っていますusing std::abs;) 。

  • コンパイラには、いくつかの浮動小数点関数の組み込み実装がある場合があります (たとえば、それらを FPU 命令に直接コンパイルします)。

ただし、コンパイラがループを完全に排除しないことに驚いています。ループ内で何も実行しないため、少なくとも の場合abs、コンパイラは副作用がないことを認識しているはずです。

于 2010-05-20T21:50:09.760 に答える
1

おそらくabsのライブラリバージョンは組み込み関数であり、その動作はコンパイラによって正確に認識されており、コンパイル時に値を計算することもできます(あなたの場合は既知であるため)、呼び出しを最適化します。実行時にのみ既知の値 (ユーザーが提供するか、2 サイクルの前に rand() で取得) でベンチマークを試す必要があります。

それでも違いがある場合は、ライブラリ abs が手品を使って手鍛造アセンブリで直接記述されているため、生成されたものよりも少し高速になる可能性があります。

于 2010-05-20T21:46:45.260 に答える
0

ライブラリ abs 関数は、明らかにフロートをテストしている間、整数で動作します。つまり、float 引数を指定した abs の呼び出しには、float から int への変換 (定数を使用しているためノーオペレーションである可能性があり、コンパイラーはコンパイル時にそれを行う可能性があります)、次に INTEGER abs 操作と変換 int->float を伴います。テンプレート化された関数にはフロートの操作が含まれますが、これはおそらく違いを生んでいます。

于 2010-05-21T06:54:06.887 に答える