8

Ho il seguente codice e mi aspetto la versione intrinseca della funzione exp() da utilizzare. Purtroppo, non è in una build x64, il che rende più lento di un simile Win32 (ad esempio, 32 bit build):Come posso ottenere un intrinseco per la funzione exp() nel codice x64?

#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'; 
} 

Sto usando le seguenti opzioni per il mio costruzione:

/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 

Come potete vedere, ho /Oi, /O2 e /fp:fast come richiesto per il MSDN article on intrinsics. Tuttavia, nonostante i miei sforzi, viene effettuata una chiamata alla libreria standard, che rende exp() più lento su build x64.

Ecco l'assembly generato:

for (double i=0;i<NUM_ITERATIONS;++i) 
000000013F911030 movsd  xmm10,mmword ptr [[email protected] (13F912248h)] 
000000013F911039 movapd  xmm8,xmm6 
000000013F91103E movapd  xmm7,xmm9 
000000013F911043 movaps  xmmword ptr [rsp+20h],xmm11 
000000013F911049 movsd  xmm11,mmword ptr [[email protected] (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) 
    } 

Come si può vedere nel montaggio sopra, c'è una chiamata fuori alla funzione exp(). Ora, diamo un'occhiata al codice generato per quella for ciclo con un 32 bit di compilazione:

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 [[email protected] (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 [[email protected] (102180h)] 
00101079 movsd  xmm2,mmword ptr [[email protected] (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 [[email protected] (102188h)] 
00101099 movsd  mmword ptr [esp+28h],xmm0 
0010109F movsd  mmword ptr [esp+30h],xmm1 
001010A5 ja   wmain+40h (101060h) 
    } 

Molto più codice di lì, ma è più veloce. Un test di temporizzazione ho fatto su un 3,3 GHz ospitante Nehalem-EP i seguenti risultati:

32 bit:

For loop body average exec time: 34.849229 cycles/10.560373 ns

64-bit: comportamento

For loop body average exec time: 45.845323 cycles/13.892522 ns

Molto strano, anzi. Perché sta succedendo?

Aggiornamento:

ho creato un Microsoft Connect bug report. Sentitevi liberi di svenderlo per ottenere una risposta autorevole da Microsoft stessa sull'uso di elementi intrinseci in virgola mobile, specialmente nel codice x64.

+0

[questo articolo] (http://blogs.msdn.com/b/ricom/archive/2009/06/10/visual-studio-why-is-there-no-64-bit-version.aspx) (spiegando perché VS non ha una versione a 64 bit) sottolinea che una build a 64 bit può essere più lenta di una a 32 bit. Non so se questa spiegazione è quella che si applica al caso specifico, però. – Attila

+1

Questo articolo riguarda una versione di Visual Studio a 64 bit, non ha nulla a che fare con la domanda posta. Esistono molti fattori che possono rendere un'applicazione a 64 bit più lenta di una a 32 bit. A meno che, mi manca qualcosa, nessuno di questi fattori ha nulla a che fare con la mia domanda sul calcolo in virgola mobile, comunque. –

+0

@MichaelGoldshteyn - il mio errore – Attila

risposta

5

su x64, aritmetica in virgola mobile viene eseguita utilizzando SSE. Questo non ha un funzionamento integrato per exp() e quindi una chiamata alla libreria standard è inevitabile. Immagino che l'articolo MSDN a cui ti riferisci sia stato scritto con codice a 32 bit che usa 8087 FP in mente.

+0

Vedere la mia domanda modificata che include il codice generato da una build a 32 bit e un confronto temporale tra 32 bit e 64 bit . Nessuna build utilizza un intrinseco "vero", ma esistono delle differenze nella funzione chiamata e la build a 32 bit è significativamente più veloce. –

+0

Beh, forse, ma rimane il fatto che non esiste alcuna intrinseca exp in nessuno dei codici operativi SSE –

+0

Questo è vero, ma mi aspettavo, per la documentazione MSDN per un'implementazione intrinseca di exp(), essere inline nel mio codice (assembly) . –

0

EDIT Vorrei aggiungere a questa discussione il collegamento a AMD's x64 instruction set manuals e Intel's reference.

Durante un'ispezione iniziale, è necessario utilizzare F2XM1 per calcolare l'esponenziale. Tuttavia, è nel set di istruzioni x87, hidden in x64 mode.

C'è speranza nel usando MMX/x87 in modo esplicito, come descritto in un post sul VirtualDub discussion boards. E, questo è how to actually write asm in VC++.

+0

Spiacente, aggiungendo '/ MD' non ha cambiato nulla ... –

0

Penso che l'unica ragione per cui Microsoft fornisce una versione intrinseca di 32-bit SSE2 exp() è le convenzioni di chiamata standard. Le convenzioni di chiamata a 32 bit richiedono che l'operando venga inserito nello stack principale e il risultato da restituire nel registro superiore dello stack FPU.Se hai abilitato la generazione del codice SSE2, è probabile che il valore restituito venga prelevato dallo stack FPU in memoria, quindi caricato da tale posizione in un registro SSE2 per qualsiasi matematica tu voglia eseguire sul risultato. Chiaramente, è più veloce passare l'operando in un registro SSE2 e restituire il risultato in un registro SSE2. Questo è ciò che fa __libm_sse2_exp(). Nel codice a 64 bit, la convenzione di chiamata standard passa l'operando e restituisce il risultato nei registri SSE2 in ogni caso, quindi non vi è alcun vantaggio nell'avere una versione intrinseca.

Il motivo della differenza di prestazioni tra SSE2 a 32 bit e implementazioni a 64 bit di exp() è che Microsoft utilizza algoritmi diversi nelle due implementazioni. Non ho idea del motivo per cui lo fanno e producono risultati diversi (diversi da 1ulp) per alcuni operandi.

Problemi correlati