Ich bemerkte eine merkwürdige Sache auf meinem Computer. * Der handschriftliche Teilbarkeitstest ist deutlich schneller als der %Bediener. Betrachten Sie das minimale Beispiel:
* 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);
}
Das Beispiel ist durch ungerade aund begrenzt m > 0. Es kann jedoch leicht auf alle aund verallgemeinert werden m. Der Code konvertiert nur die Unterteilung in eine Reihe von Ergänzungen.
Betrachten Sie nun das Testprogramm, das kompiliert wurde mit -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
}
}
... und die Ergebnisse auf meinem Computer:
| implementation | time [secs] |
|--------------------|-------------|
| divisible_ui_p | 8.52user |
| builtin % operator | 17.61user |
Daher mehr als 2 mal schneller.
Die Frage: Können Sie mir sagen, wie sich der Code auf Ihrem Computer verhält? Wird die Optimierungsmöglichkeit in GCC verpasst? Können Sie diesen Test noch schneller durchführen?
UPDATE: Wie angefordert, ist hier ein minimal reproduzierbares Beispiel:
#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;
}
kompiliert mit gcc -std=c99 -march=native -O3 -DNDEBUGauf AMD Ryzen Threadripper 2990WX mit
gcc --version
gcc (Gentoo 9.2.0-r2 p3) 9.2.0
UPDATE2: Wie angefordert, die Version, die alle verarbeiten kann aund m(wenn Sie auch einen Ganzzahlüberlauf vermeiden möchten, muss der Test mit einem ganzzahligen Typ implementiert werden, der doppelt so lang ist wie die eingegebenen Ganzzahlen):
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;
}
rIhnen berechneten s tatsächlich gleich sind.
a % bhaben bviel kleiner als a. Bei den meisten Iterationen in Ihrem Testfall sind sie ähnlich groß oder bgrößer, und Ihre Version kann in diesen Situationen auf vielen CPUs schneller sein.