アッカーマン関数

C++をいじる機会があって,前々から気になっていた定数式(constexpr)にもちょっと触れてみた.
なにやら,コンパイル時に値が決定できる値をコンパイル時に計算することで,実行時の速度が早くなるとかなんとか.

アッカーマン関数を実装した.

//ack.cpp
#include <iostream>

constexpr int ack(int m, int n) {
    return m == 0 ? n+1
        : n == 0 ? ack(m-1, 1)
        : ack(m-1, ack(m, n-1));
}

int main(void) {
    constexpr int res = ack(3, 7);

    std::cout << res << std::endl;
    return 0;
}

Ack(3,6)を求めるプログラム.
C++11では定数式に書けるものは実質return文ひとつのみらしい*1ので,条件演算子を数億年ぶりに使った.

こちらは定数式を使わないバージョン.

//ack-o.cpp
#include <iostream>

int ack(int m, int n) {
    return m == 0 ? n+1
        : n == 0 ? ack(m-1, 1)
        : ack(m-1, ack(m, n-1));
}

int main(void) {
    std::cout << ack(3, 7) << std::endl;
    return 0;
}

さて,肝心の実行速度だが,

$ time ./ack
1021

real    0m0.002s
user    0m0.000s
sys     0m0.000s

$ time ./ack-o
1021

real    0m0.011s
user    0m0.010s
sys     0m0.000s

constexprを使ったほうが実行時間は速い.ただし,ack(4,1)のように再帰深度を深くすると,コンパイル時に

ack.cpp:10:33: エラー: constexpr evaluation depth exceeds maximum of 512 (use -fconstexpr-depth= to increase the maximum)

というエラーが出る.constexpr修飾された関数は再帰深度の上限がデフォルトでは512までらしい*2.試しにめちゃめちゃ大きくしてみると通った.

$time g++ -o ack ack.cpp -fconstexpr-depth=100000000

real    0m0.777s
user    0m0.703s
sys     0m0.067s

実行時計算と比べてみると

$ time ./ack-o
65533

real    0m18.664s
user    0m18.657s
sys     0m0.003s

実行時計算と同じことをさせているのに,コンパイル時計算の方が圧倒的に速い.
何がキモなんだろう.