9

私は次のコードを持っており、exp()関数の組み込みバージョンが使用されることを期待しています。残念ながら、これはx64ビルドではないため、同様のWin32(つまり、32ビットビルド)よりも低速になります。

#include "stdafx.h"
#include <cmath>
#include <intrin.h>
#include <iostream>

int main()
{
  const int NUM_ITERATIONS=10000000;
  double expNum=0.00001;
  double result=0.0;

  for (double i=0;i<NUM_ITERATIONS;++i)
  {
    result+=exp(expNum); // <-- The code of interest is here
    expNum+=0.00001;
  }

  // To prevent the above from getting optimized out...
  std::cout << result << '\n';
}

ビルドに次のスイッチを使用しています。

/Zi /nologo /W3 /WX-
/Ox /Ob2 /Oi /Ot /Oy /GL /D "WIN32" /D "NDEBUG" 
/D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /Gm- 
/EHsc /GS /Gy /arch:SSE2 /fp:fast /Zc:wchar_t /Zc:forScope 
/Yu"StdAfx.h" /Fp"x64\Release\exp.pch" /FAcs /Fa"x64\Release\" 
/Fo"x64\Release\" /Fd"x64\Release\vc100.pdb" /Gd /errorReport:queue 

ご覧のとおり、組み込み関数に関するMSDNの記事に従って、必要に応じて/Oi/O2を持っています。それでも、私の努力にもかかわらず、標準ライブラリへの呼び出しが行われ、x64ビルドでのパフォーマンスが遅くなります。/fp:fastexp()

生成されたアセンブリは次のとおりです。

  for (double i=0;i<NUM_ITERATIONS;++i)
000000013F911030  movsd      xmm10,mmword ptr [__real@3ff0000000000000 (13F912248h)]  
000000013F911039  movapd     xmm8,xmm6  
000000013F91103E  movapd     xmm7,xmm9  
000000013F911043  movaps     xmmword ptr [rsp+20h],xmm11  
000000013F911049  movsd      xmm11,mmword ptr [__real@416312d000000000 (13F912240h)]  
  {
    result+=exp(expNum);
000000013F911052  movapd     xmm0,xmm7  
000000013F911056  call       exp (13F911A98h) // ***** exp lib call is here *****
000000013F91105B  addsd      xmm8,xmm10  
    expNum+=0.00001;
000000013F911060  addsd      xmm7,xmm9  
000000013F911065  comisd     xmm8,xmm11  
000000013F91106A  addsd      xmm6,xmm0  
000000013F91106E  jb         main+52h (13F911052h)  
  }

上記のアセンブリでわかるように、exp()関数への呼び出しがあります。for次に、 32ビットビルドでそのループ用に生成されたコードを見てみましょう。

  for (double i=0;i<NUM_ITERATIONS;++i)
00101031  xorps       xmm1,xmm1  
00101034  rdtsc  
00101036  push        ebx  
00101037  push        esi  
00101038  movsd       mmword ptr [esp+1Ch],xmm0  
0010103E  movsd       xmm0,mmword ptr [__real@3ee4f8b588e368f1 (102188h)]  
00101046  push        edi  
00101047  mov         ebx,eax  
00101049  mov         dword ptr [esp+3Ch],edx  
0010104D  movsd       mmword ptr [esp+28h],xmm0  
00101053  movsd       mmword ptr [esp+30h],xmm1  
00101059  lea         esp,[esp]  
  {
    result+=exp(expNum);
00101060  call        __libm_sse2_exp (101EC0h) // <--- Quite different from 64-bit
00101065  addsd       xmm0,mmword ptr [esp+20h]  
0010106B  movsd       xmm1,mmword ptr [esp+30h]  
00101071  addsd       xmm1,mmword ptr [__real@3ff0000000000000 (102180h)]  
00101079  movsd       xmm2,mmword ptr [__real@416312d000000000 (102178h)]  
00101081  comisd      xmm2,xmm1  
00101085  movsd       mmword ptr [esp+20h],xmm0  
    expNum+=0.00001;
0010108B  movsd       xmm0,mmword ptr [esp+28h]  
00101091  addsd       xmm0,mmword ptr [__real@3ee4f8b588e368f1 (102188h)]  
00101099  movsd       mmword ptr [esp+28h],xmm0  
0010109F  movsd       mmword ptr [esp+30h],xmm1  
001010A5  ja          wmain+40h (101060h)  
  }

そこにははるかに多くのコードがありますが、それでも高速です。3.3 GHz Nehalem-EPホストで行ったタイミングテストでは、次の結果が得られました。

32ビット:

ループ本体の平均実行時間:34.849229サイクル/ 10.560373 ns

64ビット:

ループ本体の平均実行時間:45.845323サイクル/ 13.892522 ns

確かに、非常に奇妙な振る舞いです。なぜそれが起こっているのですか?

アップデート:

MicrosoftConnectのバグレポートを作成しました。特にx64コードでの浮動小数点組み込み関数の使用について、Microsoft自体から信頼できる回答を得るために、自由に賛成してください。

4

3 に答える 3

5

x64では、浮動小数点演算はSSEを使用して実行されます。これには組み込みの操作がないexp()ため、手動でベクトル化された独自のインラインを作成しない限り、標準ライブラリへの呼び出しは避けられません__m128d exp(__m128d)SSEを使用した指数関数の最速の実装)。

あなたが参照しているMSDNの記事は、8087FPを使用する32ビットコードを念頭に置いて書かれていると思います。

于 2012-04-10T20:30:30.267 に答える
1

Microsoftが32ビットSSE2exp()の組み込みバージョンを提供する唯一の理由は、標準の呼び出し規約だと思います。32ビットの呼び出し規約では、オペランドをメインスタックにプッシュし、その結果をFPUスタックの最上位レジスタに返す必要があります。SSE2コード生成を有効にしている場合、戻り値はFPUスタックからメモリにポップされ、その場所からSSE2レジスタにロードされて、結果に対して実行したい計算が行われる可能性があります。明らかに、SSE2レジスタでオペランドを渡し、SSE2レジスタで結果を返す方が高速です。これは__libm_sse2_exp()が行うことです。64ビットコードでは、標準の呼び出し規約がオペランドを渡し、結果をSSE2レジスタに返すため、組み込みバージョンを使用する利点はありません。

exp()の32ビットSSE2実装と64ビット実装のパフォーマンスが異なる理由は、Microsoftが2つの実装で異なるアルゴリズムを使用しているためです。なぜこれを行うのかわかりません。また、一部のオペランドで異なる結果(1ulpだけ異なる)が生成されます。

于 2013-05-24T13:26:47.560 に答える
0

編集このディスカッションに、 AMDのx64命令セットマニュアルIntelのリファレンスへのリンクを追加したいと思います。

最初の検査では、F2XM1を使用して指数を計算する方法があるはずです。ただし、これはx87命令セットに含まれており、x64モードでは非表示になっています。

VirtualDubディスカッション掲示板への投稿で説明されているように、MMX/x87を明示的に使用することには希望があります。 そして、これが実際にVC++でasmを書く方法です。

于 2012-04-10T20:26:58.343 に答える