nekomimimiのblog

ねこみみみのブログ

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

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


Unity初心者なのでUnityの練習に車庫入れゲームを作りました。
とりあえず、Android版をリリースしました。こちら
Web版はこちら
iPhone版はリリース準備中です。


Screenshot_2014-12-08-20-27-25


車のゲームですが、白猫のぷにこんのように簡単に操作できるようにしました。
また、全40ステージありますが、2時間ぐらいで終わってしまう小さなボリュームです。

なお、同じようなゲームがたくさんあるので、
このゲームをGoogle Playで「駐車ゲーム」で検索しても出てきません。
自分でもアプリに探すの難しいので、ダウンロード数は0で
終わりそうな予感がします。




以下、プログラマー向けでUnityを使ってみた個人的な感想です。


ちょっとUnityを触ってみて、以下2点が便利でした。

①画面を見ながら値を変えて作れる効率の良さ
UI作成時や3Dモデルを配置時に画面を見ながら位置を変えて、
しかもプログラムを実行しながらもできるので調整しやすかったです。

オブジェクトのアクティブ・非アクティブやプロパティの値が
整理されてた形でみれて、動的に変更できるので便利でした。


②3Dモデル、スクリプト等の再利用性
うまく説明できないけど、例えば、爆発のアセットをダウンロードして、
画面に置くとすぐ爆発させられたりすぐに再利用できる。


結果、自作したプログラムは、コメントと空行を省いた有効行数で
 738行
となりした。
(行数は規模を表すのに適した尺度ではないですが、参考までに。)

作り込むとランキングとか気に入ったらツイートのような機能が必要になるので、
最小限の構成というのもあるのですが、記述量少なめですね。




はまったところ

一作、Unityでてきとうなものを前に作りましたが、
ゲームを作るのは初めてだったので、
手探りで、色々とはまりました。

以下は、実際にUnityを触ろうとする人や、検索でひかかった人向け。


①Unityの開発環境のUI
使い方がさっぱりわからなかったので、
初めはドットインストールのサイトの動画の通りに、
なんとなく動かして遊んでみた。


②どのGameObjectにスクリプトをつければいいか
ゲーム中のオブジェクトはすべてGameObjectで、
例えば車のスクリプトは車につければいい。

では、ゲーム進行管理スクリプトはどこにつければいいのか?
結局、空のGameObjectを作成してそこにつけた。


③あるスクリプトから他のスクリプト等の呼び出し
どうやって他のスクリプトを呼び出すのか、作れるようになりましたが、
色々な呼び方ができるので、どの場合に何を使えば最適なのかは、まだよくわかりません。

※参考
【Unity】特定のゲームオブジェクトやコンポーネント、スクリプトにアクセスする
[Unity]オブジェクト名からオブジェクトを取得




④JavaScriptとC#スクリプトの混在
できれば C#かJavaScriptのどちらかに統一したほうがいいですが、
アセットストアからのスクリプト等でJavaScriptとC#が混在してしまいました。
その結果、同じフォルダーにおいたのでは、その間の呼び出しができなくなりました。

そのため、呼び出される側のスクリプトを「Standard Assets」または「Plugins」フォルダ配下に置く必要がありました(コンパイル時の順序のためか?)。

※参考
[Unity] JavaScriptとC#間のアクセス (be-style)


⑤シーンを変えても前のGameObjectが残る
シーンを変えても前のシーンのオブジェクトが残ってどんどん重なってゆく現象が起きた。

調べると、シングルトンのスクリプトをつけたGameObject配下のオブジェクトが
シーンを変えても(LoadLevel)オブジェクトが消えず残っていた
そもそも、シングルトンがシーンが変わっても値を保持するので)

そのスクリプトを別の空のGameObjectにつけて解決。



⑥実行順序
シーンの開始時に実行したいけど、2つのスクリプトで一方を先に実行してほしい時がありました。
メニューでEdit→Project Settings→Script Execution orderでスクリプトの実行優先順位を指定できる。



⑦テーブルビュー(nGUI)
始めやり方がさっぱりわからなくて、ググったり動いてるサンプルをみて解析して理解した。
次からUnity標準のuGUIを使うことになりそう。


⑧Android Studioを使ったコンパイル
Unityから直接Apkを吐くと簡単なので、ネイティブ部分はプラグインを使って
作成したほうがいいです。

今回、よくわからず、自力でコンパイルしてみました。

Android Studioでコンパイルをする場合、以下の手順でした。
1.AndroidのPlayer settingsでKey store(証明書)の設定
2.Build settingsでGoogle android projectにチェックしてExport
3.Android Studioでプロジェクトをインポートする
(Import Non-Android Studio ProjectにてExportしたのとは別のフォルダにImport)
こんな感じでコンパイルしてAPKを作る。

また、Unity側のソース等を変更した場合は、以下のように反映できました。
4.上記2.のassetsフォルダの下のファイルを上記3.で作ったプロジェクトにコピー。
5.上記2.のlibs/armeabi-v7aの下のsoファイルを上記3.で作ったプロジェクトにコピー。
(もっといい方法がある予感がします。)



秋葉原の街を移動するデモを作ってみた。
unity-akiba
実際のデモは【こちら

ゼンリンから提供されているアセットを利用しました。
また、直接は使ってないですが、女の子の3Dモデルのクエリーちゃんも入れてます。
(依存性があって無いと動かなかったので。)


3Dは難しそうなので、手をださないようにしようと、心に決めてたのですが、
「Unityは良い」と何人かから聞いたので、作ってみました。

このデモは、お試しにHello World的に作ってみました。
結果、3時間ぐらいかかりました。

慣れたらどれぐらいの時間で作成できるか計測してみました。
地図上をキーボードで動くだけなら9分26秒でできました。
動かすだけだけど3Dのプログラムが短い時間でつくれてしまうUnity恐ろしい。
01 PM
(一行一行の時間はあまり正確ではありませんが、
地図のデータをインポートしてる時間が5分ほどで
一番長かったようです。)
 

直接、秋葉原の地図の3Dモデルを使ってしまったのですが、
・雨、風、晴天、夜空、曇空等の様々なエフェクト
・クエリちゃんを操作して飛行
・Nav Mesh AI Car機能
も入ってるようで、それらを使うのも楽しそう。

このページのトップヘ