C言語 Advent Calendar 2016を書く人が少ないので2日目に参加してみました。
C言語 Advent Calendar 2016 



東京都在住のHさんからの質問です。


―――――――――――――――――――――――――


switch文ているんやろか。ダラダラ続いて分解しにくいif文って感じがして・・・。離脱もちょっとわかりにくいし。


―――――――――――――――――――――――――
 

自分の試した範囲では、switchが使いにくいと思うのであれば、特に使う必要はないという結論になりました。なぜならば、ifでもswitchでも結局は同じアセンブリが出力されるから。


◇ 昔は

昔は、switch文はパフォーマンス的に必要でした。というのもif文はifの数だけ条件分岐します。そのため100個のifがあると100回判定してしまいます。

しかし、switch文はcaseの数が増えるとジャンプテーブルで1発でジャンプできる等の最適化がかかります。
ジャンプテーブルについて


そのためパフォーマンスを意識した場合にifとswitchを書き分ける必要がありました。


◇ 今は

では、今はどうでしょう。

以下のプログラムでアセンブリを比べます。

①switch文のプログラム

#include 
int main() {
  int r;
  int n;
  scanf("%d",&n);
  switch (n) {
    case 0:
      r = 10;
      break;
    case 1:
      r = 20;
      break;
    case 2:
      r = 12;
      break;
    case 3:
      r = 14;
      break;
    case 4:
      r = 19;
      break;
    default:
      r = -1;
  }
  printf("r:%d",r);
  return 0;	
}

②if文のプログラム

#include 
int main() {
  int r;
  int n;
  scanf("%d",&n);
  if (n==0) {
    r = 10;
  } else if (n == 1) {
    r = 20;
  } else if (n == 2) {
    r = 12;
  } else if (n == 3) {
    r = 14;
  } else if (n == 4) {
    r = 19;
  } else {
    r = -1;
  }
  printf("r:%d",r);
  return 0;	
}


③上記のプログラムのアセンブリ

上記の①、②の2つのプログラムはまったく一緒のアセンブリとなりました()
(diffを取ったところ全く同じでした。)

このプログラムだとジャンプテーブルすら作ってなく、データのテーブルを作って値を持ってきています。 

	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 11
	.globl	_main
	.align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	leaq	L_.str(%rip), %rdi
	leaq	-4(%rbp), %rsi
	xorl	%eax, %eax
	callq	_scanf
	movslq	-4(%rbp), %rax
	cmpq	$4, %rax
	movl	$-1, %esi
	ja	LBB0_2
## BB#1:                                ## %switch.lookup
	leaq	l_switch.table(%rip), %rcx
	movl	(%rcx,%rax,4), %esi
LBB0_2:
	leaq	L_.str.1(%rip), %rdi
	xorl	%eax, %eax
	callq	_printf
	xorl	%eax, %eax
	addq	$16, %rsp
	popq	%rbp
	retq
	.cfi_endproc

	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"%d"

L_.str.1:                               ## @.str.1
	.asciz	"r:%d"

	.section	__TEXT,__const
	.align	4                       ## @switch.table
l_switch.table:
	.long	10                      ## 0xa
	.long	20                      ## 0x14
	.long	12                      ## 0xc
	.long	14                      ## 0xe
	.long	19                      ## 0x13


.subsections_via_symbols



◇ 結論

switchとifどっちで書いても結果は一緒。読みやすい方を使えばいい。



◇ おまけ

上記を配列で書いたらどうなるでしょうか?

結局のところアセンブリ上、少し違うものの内容は同じ処理となり、結局は好きな書き方を使えば良い。


④配列でのプログラム

#include 
int main() {
  int r;
  int n;
  scanf("%d",&n);

  int arr[] = {
    10,20,12,14,19
  };

  if (n < 0 || n > 4) { //2016.12.14 コメントの指摘によりn > 5を修正。
    r = -1;
  } else {
     r = arr[n];
  }

  printf("r:%d",r);
  return 0;	
}

⑤上記のアセンブリ

	.section	__TEXT,__text,regular,pure_instructions
	.macosx_version_min 10, 11
	.globl	_main
	.align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## BB#0:
	pushq	%rbp
Ltmp0:
	.cfi_def_cfa_offset 16
Ltmp1:
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
Ltmp2:
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	leaq	L_.str(%rip), %rdi
	leaq	-4(%rbp), %rsi
	xorl	%eax, %eax
	callq	_scanf
	movslq	-4(%rbp), %rax
	cmpq	$4, %rax
	movl	$-1, %esi
	ja	LBB0_2
## BB#1:
	leaq	l_main.arr(%rip), %rcx
	movl	(%rcx,%rax,4), %esi
LBB0_2:
	leaq	L_.str.1(%rip), %rdi
	xorl	%eax, %eax
	callq	_printf
	xorl	%eax, %eax
	addq	$16, %rsp
	popq	%rbp
	retq
	.cfi_endproc

	.section	__TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
	.asciz	"%d"

	.section	__TEXT,__const
	.align	4                       ## @main.arr
l_main.arr:
	.long	10                      ## 0xa
	.long	20                      ## 0x14
	.long	12                      ## 0xc
	.long	14                      ## 0xe
	.long	19                      ## 0x13

	.section	__TEXT,__cstring,cstring_literals
L_.str.1:                               ## @.str.1
	.asciz	"r:%d"


.subsections_via_symbols

◇ 最後に

単純な例では、ifでもswitchでもarrayでも、一緒という結果になりました。
他の例でも一緒であると現状、思っています。
もし、switchで書けるもので最適化後のアセンブリでswitch有利、if有利な反例があればそのプログラムをお教えいただければありがたいです。



()アセンブリはclangで生成してます。

Apple LLVM version 8.0.0 (clang-800.0.42.1)

Target: x86_64-apple-darwin15.6.0

Thread model: posix

また、最適化O2でa.cをプログラムとして

clang -save-temps a.c -O2

とコンパイルしています。


    このエントリーをはてなブックマークに追加 mixiチェック Share on Tumblr Clip to Evernote