Slide 1

Slide 1 text

C++ で末尾再帰を最適化 したい B3 Shinonome 2022/06/03 © 2022 @Shinonome517Stu 1

Slide 2

Slide 2 text

結論 末尾再帰とは、関数のreturn 処理直前のみで再帰呼び出しを行う再 帰である C++(GNU GCC) では-O2 レベルの最適化を行うことで末尾再帰最適 化がなされる 2022/06/03 © 2022 @Shinonome517Stu 2

Slide 3

Slide 3 text

動機 C++ でも再帰を書きてぇなぁ・・・ 2022/06/03 © 2022 @Shinonome517Stu 3

Slide 4

Slide 4 text

末尾再帰最適化とは 以下三つを理解している必要がある 関数呼び出しの仕組み 再帰呼び出し 末尾関数呼び出し 2022/06/03 © 2022 @Shinonome517Stu 4

Slide 5

Slide 5 text

関数呼び出しの仕組み サブルーチンを呼ぶ際、次の命令が格納されているアドレスをスタッ クに積む(PUSH) メインルーチンに戻る際、次の命令が格納されているアドレスをスタ ックから読み取る(POP) → スタック領域が消費される 2022/06/03 © 2022 @Shinonome517Stu 5

Slide 6

Slide 6 text

出典 IPA :セキュアプログラミング講座 2022/06/03 © 2022 @Shinonome517Stu 6

Slide 7

Slide 7 text

再帰呼び出し 関数内部で、自分自身を呼び出す関数呼び出しのこと ベースケースに到達するまで、関数を呼び出し続ける → スタックオーバーフローの危険がある 2022/06/03 © 2022 @Shinonome517Stu 7

Slide 8

Slide 8 text

再帰呼び出し int fib(int n){ if(n == 0){ return 0; } else{ return fib(n-1) + fib(n-2); } } 2022/06/03 © 2022 @Shinonome517Stu 8

Slide 9

Slide 9 text

末尾関数呼び出し return 処理の直前に関数を呼び出しをしている、関数呼び出しのこと 下の例では特に再帰呼び出しでもあるので、末尾再帰呼び出しになっ ている 2022/06/03 © 2022 @Shinonome517Stu 9

Slide 10

Slide 10 text

末尾関数呼び出し int sigma(int n, int ans){ if(n <= 0){ return ans; } else{ return sigma(n - 1, ans + n); } } 2022/06/03 © 2022 @Shinonome517Stu 10

Slide 11

Slide 11 text

末尾再帰最適化 通常の再帰呼び出しでは、スタック領域を使い果たしてしまう場合が ある → 末尾再帰の場合に限って、ただのジャンプ命令にすることができる → 末尾再帰最適化 2022/06/03 © 2022 @Shinonome517Stu 11

Slide 12

Slide 12 text

C++ で末尾再帰を最適化する 和を求める関数を末尾再帰で実装 ​ k k=1 ∑ n 2022/06/03 © 2022 @Shinonome517Stu 12

Slide 13

Slide 13 text

ソースコード #include #include using namespace std; int64_t sigma(int64_t n, int64_t ans){ if(n <= 0) return ans; else return sigma(n - 1, ans + n); } int main(){ cout << "sigma(100): " << sigma(100, 0) << endl; cout << "sigma(10000000): " << (int64_t)sigma(10000000, 0) << endl; } 2022/06/03 © 2022 @Shinonome517Stu 13

Slide 14

Slide 14 text

コンパイル gdb を用いるので-g オプションをつける 以下3 パターンのコンパイルを試す 2022/06/03 © 2022 @Shinonome517Stu 14

Slide 15

Slide 15 text

最適化オプションなし g++ -g -Wall sigma-rec.cpp -o no-opt.out O1 最適化オプション g++ -g -Wall -O1 sigma-rec.cpp -o opt.out O2 最適化オプション g++ -g -Wall -O2 sigma-rec.cpp -o opt2.out 2022/06/03 © 2022 @Shinonome517Stu 15

Slide 16

Slide 16 text

実行結果 上から順に「最適化オプションなし」, 「O1 最適化オプション」, 「O2 最適化オプション」の実行結果 →O2 最適化オプションを付した実行ファイルのみ、正しく実行できて いる 2022/06/03 © 2022 @Shinonome517Stu 16

Slide 17

Slide 17 text

実行結果 2022/06/03 © 2022 @Shinonome517Stu 17

Slide 18

Slide 18 text

gdb 解析結果 ターミナルで gdb ./"file name" (gdb) disass sigma でsigma 関数を逆アセンブルした 2022/06/03 © 2022 @Shinonome517Stu 18

Slide 19

Slide 19 text

最適化オプションなし 2022/06/03 © 2022 @Shinonome517Stu 19

Slide 20

Slide 20 text

O1 最適化オプション 余分な処理は消されているようだが、call 命令(関数呼び出し)は行 われている 2022/06/03 © 2022 @Shinonome517Stu 20

Slide 21

Slide 21 text

O2 最適化オプション call 命令(関数呼び出し命令)が消えている 2022/06/03 © 2022 @Shinonome517Stu 21

Slide 22

Slide 22 text

感想 最適化処理すげー 末尾再帰最適化を行える環境では、積極的に末尾再帰を利用したい 2022/06/03 © 2022 @Shinonome517Stu 22

Slide 23

Slide 23 text

参考資料 関数呼び出しの仕組み IPA  セキュア・プログラミング講座 CodeZine インラインアセンブラで学ぶアセンブリ言語 第3 回 GNU g++ 最適化オプション 2022/06/03 © 2022 @Shinonome517Stu 23

Slide 24

Slide 24 text

ご清聴ありがとうございました 2022/06/03 © 2022 @Shinonome517Stu 24

Slide 25

Slide 25 text

おまけ 処理系が違えば、最適化の方法も異なる 以下はClang(Apple) でコンパイルされたsigma 関数を逆アセンブルし た結果 2022/06/03 © 2022 @Shinonome517Stu 25

Slide 26

Slide 26 text

最適化オプションなし 2022/06/03 © 2022 @Shinonome517Stu 26

Slide 27

Slide 27 text

O1 最適化オプション 2022/06/03 © 2022 @Shinonome517Stu 27

Slide 28

Slide 28 text

O2 最適化オプション 2022/06/03 © 2022 @Shinonome517Stu 28