ちょっとしたテストを書き、gcc -O4
最適化してコンパイルしました。
add_index_mod
このテストのadd_index_if
実装は次のとおりです。
void add_index_mod(int *p) {
*p = (*p + 1) % 10;
}
void add_index_if(int *p) {
if (*p == 9)
*p = 0;
else
(*p)++;
}
そして、それは私が得たものですadd_index_mod
:
mov eax, dword [rdi]
mov edx, 0x66666667
lea ecx, dword [rax + 1]
mov eax, ecx
imul edx
mov eax, ecx
sar eax, 0x1f
sar edx, 2
sub edx, eax
lea eax, dword [rdx + rdx*4]
add eax, eax
sub ecx, eax
mov dword [rdi], ecx
ret
ここで、コンパイラが div を一連の mul、shift、および sub に置き換えたことがわかります。このトリックについては、こちらで詳しく説明しています。
そして、それは私が得たものですadd_index_if
:
mov edx, dword [rdi]
lea eax, dword [rdx + 1]
cmp edx, 9
mov edx, 0
cmove eax, edx
mov dword [rdi], eax
ret
ここでは、cmp と条件付き mov だけで特別なことは何もありません。
これで、この表を使用して、この両方の関数のアセンブリ コードの効率を計算することができます。しかし、これは順不同の実行、分岐予測などのため、最善の方法ではありません。
上で述べたように、ちょっとしたテストを書きました。
#include <stdio.h>
#include <stdint.h>
#define REPEATS (1 << 30)
static inline uint64_t rdtsc() {
unsigned int hi, lo;
__asm__ volatile("rdtsc" : "=a" (lo), "=d" (hi));
return ((uint64_t)hi << 32) | lo;
}
void add_index_mod(int *p) {
*p = (*p + 1) % 10;
}
void add_index_if(int *p) {
if (*p == 9)
*p = 0;
else
(*p)++;
}
int main() {
int p = 0;
uint32_t i;
uint64_t start, stop;
double delta, ticks_per_call;
// mod ================================
start = rdtsc();
for (i = 0; i < REPEATS; ++i) {
add_index_mod(&p);
}
stop = rdtsc();
// gcc with -O4 can remove above loop
// if we don't use its result so print it
printf("%d\n", p);
delta = (double)(stop - start);
ticks_per_call = delta / REPEATS;
printf("add_index_mod: %f\n", ticks_per_call);
// if ================================
start = rdtsc();
for (i = 0; i < REPEATS; ++i) {
add_index_if(&p);
}
stop = rdtsc();
printf("%d\n", p);
delta = (double)(stop - start);
ticks_per_call = delta / REPEATS;
printf("add_index_if: %f\n", ticks_per_call);
return 0;
}
Intel Core i5-6500 の出力は次のとおりです。
add_index_mod: 9.643092
add_index_if: 2.063125
したがって、膨大な数の呼び出しに対して、私の CPUadd_index_if
よりも 5 倍高速です。add_index_mod