http://www.initialt.org/takehiro-switch-case.PDF
いまどきのCPUには漏れなく分岐予測があるので予測が当る可能性が高いものからif文を書くのが良いという話。
gccにはプロファイルをもとに最適化する機能があるので、どこまでやってくれるか試してみた。
// main.c #include <stdio.h> #include <stdlib.h> extern int f(int); int main(int ac, char **av) { const int x = atoi(av[1]); printf("%d -> %d\n", x, func(x)); return 0; }
// func.c extern int func0(void); extern int func2(void); extern int func3(void); int func(int x) { if (x < 2) return func0(); if (x == 2) return func2(); return func3(); }
// impl.c int func0(void) { return 0; } int func2(void) { return 2; } int func3(void) { return 3; }
# Makefile GCC = gcc47 CFLAGS0 = -O2 CFLAGS1 = -O2 -fprofile-generate CFLAGS2 = -O2 -fprofile-use OBJS = main.o func.o impl.o all: $(MAKE) clean $(MAKE) pgo CFLAGS="$(CFLAGS0)" cat func.s >func.s0 $(MAKE) clean $(MAKE) pgo CFLAGS="$(CFLAGS1)" cat func.s >func.s1 $(MAKE) test-run $(MAKE) clean0 $(MAKE) pgo CFLAGS="$(CFLAGS2)" cat func.s >func.s2 pgo: $(OBJS) $(GCC) -o $@ $(CFLAGS) $(OBJS) .c.o: $(GCC) -S $(CFLAGS) $< $(GCC) -c $(CFLAGS) $< test-run: ./pgo 0 ./pgo 1 ./pgo 2 ./pgo 3 clean0: -rm -f pgo *.o clean: clean0 -rm -f *.s *.s? *.gc??
普通にコンパイルしたfuncのアセンブリコード(func.s0)
.file "func.c" .text .p2align 4,,15 .globl func .type func, @function func: .LFB0: .cfi_startproc cmpl $1, %edi jle .L5 cmpl $2, %edi je .L6 jmp func3 .p2align 4,,10 .L6: jmp func2 .p2align 4,,10 .L5: jmp func0 .cfi_endproc .LFE0: .size func, .-func .ident "GCC: (FreeBSD Ports Collection) 4.7.1 20120324 (prerelease)" .section .note.GNU-stack,"",@progbits
ソースコードどおりに生成されているようだ。
PGO付きでコンパイルしたfuncのアセンブリコード(func.s2)
.file "func.c" .text .p2align 4,,15 .globl func .type func, @function func: .LFB0: .cfi_startproc cmpl $1, %edi jg .L2 jmp func0 .L2: cmpl $2, %edi jne .L3 .p2align 4,,5 jmp func2 .L3: .p2align 4,,5 jmp func3 .cfi_endproc .LFE0: .size func, .-func .ident "GCC: (FreeBSD Ports Collection) 4.7.1 20120324 (prerelease)" .section .note.GNU-stack,"",@progbits
逆アセンブルするとこんな感じか?
int func(int x) { if (x > 1) { if (x != 2) { return func3(); } return func2(); } return func0(); }
順番はかなり入れ換わっているが、これだと分岐予測が当らないからよくないなぁ。(早い話、分岐予測的にはまったく改善されてない。)