Domanda

In un app sto profiling, ho scoperto che in alcuni casi questa funzione è in grado di prendere in consegna il 10% del tempo totale di esecuzione.

Ho visto la discussione nel corso degli anni di implementazioni più veloci sqrt utilizzando subdolo virgola mobile inganno, ma non so se queste cose sono vecchie sulle CPU moderne.

MSVC ++ 2008 compilatore viene utilizzato, per riferimento ... anche se mi piacerebbe pensare sqrt non sta per aggiungere overhead molto però.

vedi anche qui per la discussione simile su modf funzione.

EDIT: per riferimento, questo è un metodo ampiamente utilizzato, ma è in realtà molto più veloce? Quanti cicli è SQRT comunque in questi giorni?

È stato utile?

Soluzione

Si, è possibile anche senza inganno:

1) la precisione sacrificio per la velocità:. L'algoritmo sqrt è iterativo, ri-implementare con un minor numero di iterazioni

2) le tabelle di ricerca:. O solo per il punto di inizio dell'iterazione, che in combinazione con l'interpolazione per farti tutto il tragitto

3) la cache: stai sqrting sempre lo stesso insieme limitato di valori? in caso affermativo, la cache può funzionare bene. Ho trovato questo utile nelle applicazioni di grafica in cui la stessa cosa viene calcolato per un sacco di forme della stessa dimensione, quindi i risultati possono essere utilmente nella cache.

Altri suggerimenti

C'è una grande tabella di confronto qui: http://assemblyrequired.crashworks.org/timing-square-root/

Per farla breve, ssqrts di SSE2 è di circa 2 volte più veloce di FPU fsqrt, e un'approssimazione + iterazione è circa 4 volte più veloce rispetto a quella (8x nel complesso).

Inoltre, se si sta cercando di prendere uno sqrt singola precisione, assicurarsi che in realtà è quello che stai ricevendo. Ho sentito parlare di almeno un compilatore che converte gli argomenti float ad un doppio, chiamata sqrt doppia precisione, poi riconvertire galleggiante.

Sei molto probabile per guadagnare più miglioramenti di velocità modificando il tuo algoritmi che cambiando la loro implementazioni : tenta di chiamare sqrt() meno, invece di effettuare le chiamate più velocemente. (E se pensate che questo non è possibile - i miglioramenti per sqrt() di cui parli sono solo questo: miglioramenti del algoritmo utilizzato per calcolare la radice quadrata.)

Dal momento che viene utilizzato molto spesso, è probabile che l'attuazione del vostro libreria standard di sqrt() è quasi ottimale per il caso generale. A meno che non si dispone di un dominio limitato (per esempio, se avete bisogno di meno precisione) in cui l'algoritmo può prendere alcune scorciatoie, si tratta di qualcuno molto improbabile esce con un'implementazione che è più veloce.

Si noti che, dal momento che la funzione utilizza il 10% del tempo di esecuzione, anche se si riesce a venire con un'implementazione che richiede solo il 75% del tempo di std::sqrt(), questo ancora sarà solo portare il tempo di esecuzione dalla 2,5% . Per la maggior parte delle applicazioni degli utenti non sarebbe nemmeno notare questo, a meno che non utilizzano un orologio per misurare.

Che livello di precisione non è necessario il sqrt di essere? È possibile ottenere approssimazioni ragionevoli molto rapidamente: vedi eccellente inversa piazza funzione radice di ispirazione (nota del Quake3 che il codice è GPL, quindi non si può decidere di integrare direttamente).

Non so se risolto questo, ma ho letto su di esso prima, e sembra che la cosa più veloce da fare è sostituire la funzione sqrt con una versione assembly inline;

è possibile vedere una descrizione di un carico di alternative qui .

Il migliore è questo frammento di magia:

double inline __declspec (naked) __fastcall sqrt(double n)
{
    _asm fld qword ptr [esp+4]
    _asm fsqrt
    _asm ret 8
} 

Si tratta di 4.7x più veloce della chiamata sqrt di serie con la stessa precisione.

Ecco un modo veloce con un look up table di soli 8 KB. L'errore è ~ 0,5% del risultato. Si può facilmente allargare la tabella, riducendo così l'errore. Corre circa 5 volte più veloce del normale sqrt ()

// LUT for fast sqrt of floats. Table will be consist of 2 parts, half for sqrt(X) and half for sqrt(2X).
const int nBitsForSQRTprecision = 11;                       // Use only 11 most sagnificant bits from the 23 of float. We can use 15 bits instead. It will produce less error but take more place in a memory. 
const int nUnusedBits   = 23 - nBitsForSQRTprecision;       // Amount of bits we will disregard
const int tableSize     = (1 << (nBitsForSQRTprecision+1)); // 2^nBits*2 because we have 2 halves of the table.
static short sqrtTab[tableSize]; 
static unsigned char is_sqrttab_initialized = FALSE;        // Once initialized will be true

// Table of precalculated sqrt() for future fast calculation. Approximates the exact with an error of about 0.5%
// Note: To access the bits of a float in C quickly we must misuse pointers.
// More info in: http://en.wikipedia.org/wiki/Single_precision
void build_fsqrt_table(void){
    unsigned short i;
    float f;
    UINT32 *fi = (UINT32*)&f;

    if (is_sqrttab_initialized)
        return;

    const int halfTableSize = (tableSize>>1);
    for (i=0; i < halfTableSize; i++){
         *fi = 0;
         *fi = (i << nUnusedBits) | (127 << 23); // Build a float with the bit pattern i as mantissa, and an exponent of 0, stored as 127

         // Take the square root then strip the first 'nBitsForSQRTprecision' bits of the mantissa into the table
         f = sqrtf(f);
         sqrtTab[i] = (short)((*fi & 0x7fffff) >> nUnusedBits);

         // Repeat the process, this time with an exponent of 1, stored as 128
         *fi = 0;
         *fi = (i << nUnusedBits) | (128 << 23);
         f = sqrtf(f);
         sqrtTab[i+halfTableSize] = (short)((*fi & 0x7fffff) >> nUnusedBits);
    }
    is_sqrttab_initialized = TRUE;
}

// Calculation of a square root. Divide the exponent of float by 2 and sqrt() its mantissa using the precalculated table.
float fast_float_sqrt(float n){
    if (n <= 0.f) 
        return 0.f;                           // On 0 or negative return 0.
    UINT32 *num = (UINT32*)&n;
    short e;                                  // Exponent
    e = (*num >> 23) - 127;                   // In 'float' the exponent is stored with 127 added.
    *num &= 0x7fffff;                         // leave only the mantissa 

    // If the exponent is odd so we have to look it up in the second half of the lookup table, so we set the high bit.
    const int halfTableSize = (tableSize>>1);
    const int secondHalphTableIdBit = halfTableSize << nUnusedBits;
    if (e & 0x01) 
        *num |= secondHalphTableIdBit;  
    e >>= 1;                                  // Divide the exponent by two (note that in C the shift operators are sign preserving for signed operands

    // Do the table lookup, based on the quaternary mantissa, then reconstruct the result back into a float
    *num = ((sqrtTab[*num >> nUnusedBits]) << nUnusedBits) | ((e + 127) << 23);
    return n;
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top