Ho notato una cosa curiosa sul mio computer. * Il test di divisibilità scritto a mano è significativamente più veloce %dell'operatore. Considera l'esempio minimo:
* AMD Ryzen Threadripper 2990WX, GCC 9.2.0
static int divisible_ui_p(unsigned int m, unsigned int a)
{
if (m <= a) {
if (m == a) {
return 1;
}
return 0;
}
m += a;
m >>= __builtin_ctz(m);
return divisible_ui_p(m, a);
}
L'esempio è limitato da dispari ae m > 0. Tuttavia, può essere facilmente generalizzato a tutti ae m. Il codice converte la divisione in una serie di aggiunte.
Consideriamo ora il programma di test compilato con -std=c99 -march=native -O3:
for (unsigned int a = 1; a < 100000; a += 2) {
for (unsigned int m = 1; m < 100000; m += 1) {
#if 1
volatile int r = divisible_ui_p(m, a);
#else
volatile int r = (m % a == 0);
#endif
}
}
... e i risultati sul mio computer:
| implementation | time [secs] |
|--------------------|-------------|
| divisible_ui_p | 8.52user |
| builtin % operator | 17.61user |
Pertanto più di 2 volte più veloce.
La domanda: puoi dirmi come si comporta il codice sul tuo computer? Ha perso l'opportunità di ottimizzazione in GCC? Puoi fare questo test ancora più velocemente?
AGGIORNAMENTO: Come richiesto, ecco un esempio riproducibile minimo:
#include <assert.h>
static int divisible_ui_p(unsigned int m, unsigned int a)
{
if (m <= a) {
if (m == a) {
return 1;
}
return 0;
}
m += a;
m >>= __builtin_ctz(m);
return divisible_ui_p(m, a);
}
int main()
{
for (unsigned int a = 1; a < 100000; a += 2) {
for (unsigned int m = 1; m < 100000; m += 1) {
assert(divisible_ui_p(m, a) == (m % a == 0));
#if 1
volatile int r = divisible_ui_p(m, a);
#else
volatile int r = (m % a == 0);
#endif
}
}
return 0;
}
compilato con gcc -std=c99 -march=native -O3 -DNDEBUGAMD Ryzen Threadripper 2990WX con
gcc --version
gcc (Gentoo 9.2.0-r2 p3) 9.2.0
UPDATE2: come richiesto, la versione in grado di gestire qualsiasi ae m(se si desidera anche evitare l'overflow di numeri interi, il test deve essere implementato con il tipo intero due volte più lungo degli interi di input):
int divisible_ui_p(unsigned int m, unsigned int a)
{
#if 1
/* handles even a */
int alpha = __builtin_ctz(a);
if (alpha) {
if (__builtin_ctz(m) < alpha) {
return 0;
}
a >>= alpha;
}
#endif
while (m > a) {
m += a;
m >>= __builtin_ctz(m);
}
if (m == a) {
return 1;
}
#if 1
/* ensures that 0 is divisible by anything */
if (m == 0) {
return 1;
}
#endif
return 0;
}
rs che calcoli sono effettivamente uguali tra loro.
a % bha bmolto più piccoli di a. Attraverso la maggior parte delle iterazioni nel tuo test case, sono di dimensioni simili o bsono più grandi e la tua versione può essere più veloce su molte CPU in quelle situazioni.