Bilgisayarımda ilginç bir şey fark ettim. * El yazısı bölünebilirlik testi %
operatörden önemli ölçüde daha hızlıdır . Minimum örneği düşünün:
* 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);
}
Örnek tek a
ve ile sınırlıdır m > 0
. Ancak, herkes için kolayca genelleştirilebilir a
ve m
. Kod, bölümü sadece bir dizi eklemeye dönüştürür.
Şimdi aşağıdakilerle derlenen test programını düşünün -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
}
}
... ve bilgisayarımdaki sonuçları:
| implementation | time [secs] |
|--------------------|-------------|
| divisible_ui_p | 8.52user |
| builtin % operator | 17.61user |
Bu nedenle 2 kat daha hızlı.
Soru: Kodun makinenizde nasıl davrandığını söyleyebilir misiniz? GCC'de kaçırılan optimizasyon fırsatı var mı? Bu testi daha da hızlı yapabilir misin?
GÜNCELLEME: İstendiği gibi, burada tekrarlanabilir minimum bir örnek verilmiştir:
#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;
}
ile gcc -std=c99 -march=native -O3 -DNDEBUG
AMD Ryzen Threadripper 2990WX ile derlendi
gcc --version
gcc (Gentoo 9.2.0-r2 p3) 9.2.0
GÜNCELLEME2: İstendiği gibi, herhangi birini işleyebilen sürüm a
ve m
(ayrıca tamsayı taşmasını önlemek istiyorsanız, test, girdi tamsayılarının iki katı kadar tamsayı türüyle uygulanmalıdır):
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;
}
r
, hesapladığınız iki s'nin gerçekten birbirine eşit olduğunu iddia ettiğiniz bir test de görmek istiyorum .
a % b
var b
çok daha küçük a
. Test durumunuzdaki çoğu yinelemeyle, benzer boyutta veya b
daha büyüktür ve sürümünüz bu durumlarda birçok CPU'da daha hızlı olabilir.