2012-04-30 13 views
6

C'è una grande (~ 100 000) array di galleggiante variabili punto, e v'è una soglia (anche floating punto).efficiente Confronto virgola mobile (Cortex-A8)

Il problema è che devo confrontare ciascuna variabile della matrice con una soglia, ma il trasferimento di flag NEON richiede molto tempo (~ 20 cicli in base a un profiler).

Esiste un modo efficace per confrontare questi valori?

NOTA: errore di arrotondamento non importa, ho provato il seguente:

float arr[10000]; 
float threshold; 
.... 

int a = arr[20]; // e.g. 
int t = threshold; 
if (t > a) {....} 

Ma in questo caso ottengo la seguente sequenza comando processore:

vldr.32  s0, [r0] 
vcvt.s32.f32 s0, s0 
vmov   r0, s0 <--- takes 20 cycles as `vmrs APSR_nzcv, fpscr` in case of 
cmp   r0, r1   floating point comparison 

Come conversione avviene a NEON, non ho importanza se confronti gli interi, per modo descritto o float.

+0

Le persone su codereview.stackexchange.com potrebbero dover dire qualcosa anche a questo. – PlasmaHH

+3

Il tuo codice è incoerente con la tua affermazione di problema: i dati sono a virgola mobile ma tu mostri la soglia come int - anche tu lanci ciascun valore di dati float a int - perché? Se i dati sono float, la soglia dovrebbe essere float e dovresti fare un confronto float (cioè nessuna conversione int-float). Inoltre, che cosa intendete fare con valori superiori alla (o inferiore) soglia (questo determinerà se NEON è appropriato o meno)? –

+2

Molte persone abbandonano NEON per essere più lente di ARM senza sapere cosa evitare e come programmare correttamente la SIMD. A seconda di cosa vuoi esattamente, non è possibile iniziare con SIMD o non sai come gestire if-else con NEON. –

risposta

5

Se carri sono a 32 bit IEEE-754 e interi sono a 32-bit e anche se non ci sono + infinito, -infinity e NaN valori, possiamo confrontare i galleggianti come interi con un piccolo trucco:

#include <stdio.h> 
#include <limits.h> 
#include <assert.h> 

#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1] 
C_ASSERT(sizeof(int) == sizeof(float)); 
C_ASSERT(sizeof(int) * CHAR_BIT == 32); 

int isGreater(float* f1, float* f2) 
{ 
    int i1, i2, t1, t2; 

    i1 = *(int*)f1; 
    i2 = *(int*)f2; 

    t1 = i1 >> 31; 
    i1 = (i1^t1) + (t1 & 0x80000001); 

    t2 = i2 >> 31; 
    i2 = (i2^t2) + (t2 & 0x80000001); 

    return i1 > i2; 
} 

int main(void) 
{ 
    float arr[9] = { -3, -2, -1.5, -1, 0, 1, 1.5, 2, 3 }; 
    float thr; 
    int i; 

    // Make sure floats are 32-bit IEE754 and 
    // reinterpreted as integers as we want/expect 
    { 
    static const float testf = 8873283.0f; 
    unsigned testi = *(unsigned*)&testf; 
    assert(testi == 0x4B076543); 
    } 

    thr = -1.5; 
    for (i = 0; i < 9; i++) 
    { 
    printf("%f %s %f\n", arr[i], "<=\0> " + 3*isGreater(&arr[i], &thr), thr); 
    } 

    thr = 1.5; 
    for (i = 0; i < 9; i++) 
    { 
    printf("%f %s %f\n", arr[i], "<=\0> " + 3*isGreater(&arr[i], &thr), thr); 
    } 

    return 0; 
} 

uscita:

-3.000000 <= -1.500000 
-2.000000 <= -1.500000 
-1.500000 <= -1.500000 
-1.000000 > -1.500000 
0.000000 > -1.500000 
1.000000 > -1.500000 
1.500000 > -1.500000 
2.000000 > -1.500000 
3.000000 > -1.500000 
-3.000000 <= 1.500000 
-2.000000 <= 1.500000 
-1.500000 <= 1.500000 
-1.000000 <= 1.500000 
0.000000 <= 1.500000 
1.000000 <= 1.500000 
1.500000 <= 1.500000 
2.000000 > 1.500000 
3.000000 > 1.500000 

Naturalmente, ha senso che precalculate valore intero finale nel isGreater() che viene utilizzato nella operatore di confronto, se la soglia non cambia.

Se si ha paura del comportamento non definito in C/C++ nel codice precedente, è possibile riscrivere il codice in assembly.

+0

Sembra un'ottima idea. Sto ancora affrontando il problema con vmov.32 ma principalmente è una buona idea. Grazie. – Alex

+0

Soluzione funziona. – Alex

+0

@vasile: di quale bug stai parlando? Cos'è complesso? Se lo è, come lo rendi più semplice? –

2

Se i dati sono a virgola mobile, si dovrebbero fare i confronti con i galleggianti, ad es.

float arr[10000]; 
float threshold; 
.... 

float a = arr[20]; // e.g. 
if (threshold > a) {....} 

altrimenti si avranno costose conversioni float-int.

+0

Se faccio un confronto tra 2 float provoca costosi trasferimenti flag-register. Ecco perché ho provato a fare un confronto tra 2 numeri interi. – Alex

+0

Quali operazioni eseguite successivamente quando il test della soglia è vero/falso? –

+0

vcmpe.f32 s17, s16 vmrs APSR_nzcv, fpscr Se ho ricevuto la tua domanda. – Alex

2

Il vostro esempio mostra quanto male i codici generati dal compilatore possono essere:

si carica un valore con NEON solo per convertirlo in int, poi fa un NEON-> trasferimento ARM che provoca un colore gasdotto con conseguente 11 ~ 14 cicli sprecati.

La soluzione migliore sarebbe scrivere la funzione completamente in mano.

Tuttavia, v'è un semplice accorgimento che permette confronti veloce galleggiante senza typecasting E troncamento:

soglia positiva (esattamente veloce come confronto int):

void example(float * pSrc, float threshold, unsigned int count) 
{ 
    typedef union { 
    int ival, 
    unsigned int uval, 
    float fval 
    } unitype; 

    unitype v, t; 
    if (count==0) return; 
    t.fval = threshold; 
    do { 
    v.fval = *pSrc++; 
    if (v.ival < t.ival) { 
     // your code here 
    } 
    else { 
     // your code here (optional) 
    } 
    } while (--count); 
} 

soglia negativa (1 ciclo più per valore rispetto al confronto):

void example(float * pSrc, float threshold, unsigned int count) 
{ 
    typedef union { 
    int ival, 
    unsigned int uval, 
    float fval 
    } unitype; 

    unitype v, t, temp; 
    if (count==0) return; 
    t.fval = threshold; 
    t.uval &= 0x7fffffff; 
    do { 
    v.fval = *pSrc++; 
    temp.uval = v.uval^0x80000000; 
    if (temp.ival >= t.ival) { 
     // your code here 
    } 
    else { 
     // your code here (optional) 
    } 
    } while (--count); 
} 

Penso che sia molto più veloce di quello accettato sopra. Di nuovo, sono un po 'in ritardo.