Sto rivisitando questa domanda cinque anni dopo, poiché questo è rilevante anche per me. Ho eseguito alcune misurazioni delle prestazioni su due versioni pure C e due inline-assembly per x86-64, e i risultati potrebbero essere interessanti.
Le varianti testate della divisione pavimenti sono:
- L'implementazione ho usato per qualche tempo;
- La leggera variante su quella presentata sopra che utilizza solo una divisione;
- Il precedente, ma implementato manualmente in inline-assembly; e
- Una versione
CMOV
implementata in assembly.
Quello che segue è il mio programma di riferimento:
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#ifndef VARIANT
#define VARIANT 3
#endif
#if VARIANT == 0
#define floordiv(a, b) (((a) < 0)?((((a) + 1)/(b)) - 1):((a)/(b)))
#elif VARIANT == 1
#define floordiv(a, b) ((((a) < 0)?((a) - ((b) - 1)):(a))/(b))
#elif VARIANT == 2
#define floordiv(a, b) ({ \
int result; \
asm("test %%eax, %%eax; jns 1f; sub %1, %%eax;" \
"add $1, %%eax; 1: cltd; idivl %1;" \
: "=a" (result) \
: "r" (b), \
"0" (a) \
: "rdx"); \
result;})
#elif VARIANT == 3
#define floordiv(a, b) ({ \
int result; \
asm("mov %%eax, %%edx; sub %1, %%edx; add $1, %%edx;" \
"test %%eax, %%eax; cmovs %%edx, %%eax; cltd;" \
"idivl %1;" \
: "=a" (result) \
: "r" (b), \
"0" (a) \
: "rdx"); \
result;})
#endif
double ntime(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return(tv.tv_sec + (((double)tv.tv_usec)/1000000.0));
}
void timediv(int n, int *p, int *q, int *r)
{
int i;
for(i = 0; i < n; i++)
r[i] = floordiv(p[i], q[i]);
}
int main(int argc, char **argv)
{
int n, i, *q, *p, *r;
double st;
n = 10000000;
p = malloc(sizeof(*p) * n);
q = malloc(sizeof(*q) * n);
r = malloc(sizeof(*r) * n);
for(i = 0; i < n; i++) {
p[i] = (rand() % 1000000) - 500000;
q[i] = (rand() % 1000000) + 1;
}
st = ntime();
for(i = 0; i < 100; i++)
timediv(n, p, q, r);
printf("%g\n", ntime() - st);
return(0);
}
ho compilato questo con gcc -march=native -Ofast
usando GCC 4.9.2, ed i risultati, il mio core i5-2400, sono stati i seguenti. I risultati sono abbastanza riproducibili da una corsa all'altra - atterrano sempre nello stesso ordine, almeno.
- Variante 0: 7.21 secondi
- Variante 1: 7,26 secondi
- Variante 2: 6.73 secondi
- Variante 3: 4.32 secondi
Così l'attuazione CMOV
soffia gli altri fuori l'acqua, almeno. Ciò che mi sorprende è che la variante 2 out-fa la sua versione in puro C (variante 1) con un margine abbastanza ampio. Avrei pensato che il compilatore dovesse essere in grado di emettere codice almeno efficiente quanto il mio.
Qui ci sono alcune altre piattaforme, per il confronto:
AMD Athlon 64 X2 4200+, GCC 4.7.2:
- Variante 0: 26.33 secondi
- Variante 1: 25,38 secondi
- Variante 2: 25.19 secondi
- Variante 3: 22.39 secondi
Xeon E3-1271 v3, GCC 4.9.2:
- Variante 0: 5.95 secondi
- Variante 1: 5,62 secondi
- Variante 2: 5.40 secondi
- Variante 3: 3,44 secondi
Si noti che Varianti 0 e 1 (non hanno controllato gli altri) forniscono solo i risultati numericamente corretti per la divisione del piano quando il divisore è positivo. (E in tal caso, i risultati coincidono anche con la divisione euclidea.) Se per esempio a, b = 5, -3, queste formule dicono che il quoziente è -1, ma il risultato per la divisione floor dovrebbe essere -2. (La divisione euclidea per 5, -3 è -1, ma queste formule danno una risposta diversa rispetto alla divisione sia floor che euclidea quando entrambi gli operandi sono negativi.) – dubiousjim
Hai versioni benchmark in cui il divisore è una costante positiva (ma il dividendo è calcolato in fase di esecuzione)? Per il potere di due dividendi, l'operatore di turno a destra sarebbe quasi certamente il più veloce di qualsiasi altra alternativa, e probabilmente più leggibile di qualsiasi cosa che sarebbe altrettanto efficiente. Per altri dividendi, il codice ottimale per la divisione floored/Euclidian dovrebbe essere più veloce del codice per troncato, ma non conosco alcun modo affidabile per convincere i compilatori a generare un buon codice. – supercat
@supercat: per i divisori costanti in fase di compilazione, il compilatore sarà in grado di generare codice migliore indipendentemente dal fatto che sia un power-of-two o meno, utilizzando [questo trucco] (https://stackoverflow.com/questions/30790184/perform -integer-divisione-using-moltiplicazione). Quindi sì, le divisioni con i divisori costanti in fase di compilazione saranno sicuramente più veloci di qualsiasi algoritmo che gestisca i divisori dinamici. – Dolda2000