バカ正直に計算しようとするとすぐ計算量が膨大になり、計算できなくなるたぐいの関数です。
これをアセンブリでバカ正直に計算させるコードを書いてみました。階乗を計算させるコードのおまけ付きです。
実際に実行させたところ、ack(3, 12) あたりになるとスタックがあふれました。
スタックサイズを調整すればもうちょっといけると思います。
使用した高速化のテクニック
- 末尾再帰を CALL から JMP に置き換え
- TEST+JE でmacro fusion できる命令形式にした
- INC / DEC を ADD / SUB に置き換えて、フラグレジスタの依存関係を解消
- MOV RDX, 1 を MOV EDX, 1 にして命令長を短縮
.code ack PROC public ; RCX = m, RDX = n TEST RCX, RCX JE m_zero TEST RDX, RDX JE n_zero PUSH RCX SUB RDX, 1 CALL ack POP RCX SUB RCX, 1 MOV RDX, RAX JMP ack n_zero: MOV EDX, 1 SUB RCX, 1 JMP ack m_zero: MOV RAX, RDX ADD RAX, 1 RET ack ENDP factorial PROC public MOV EAX, 1 CMP RCX, 1 JLE f_ret f_loop: IMUL RAX, RCX SUB RCX, 1 CMP RCX, 1 JNLE f_loop f_ret: RET factorial ENDP END
一応 C で書いた以下のコードよりは、3割ぐらい速いようです(Visual C++ 2010 で /O2 としてコンパイル)。階乗は微妙に速いだけでほとんど変わりませんでした。CPU は Penryn ベースの Core2 Duo です。
int64_t factorial_c(int64_t n) { int64_t r = 1; while(n > 1) r *= n--; return r; } int64_t ack_c(int64_t m, int64_t n) { return m == 0 ? n + 1 : n == 0 ? ack_c(m - 1, 1) : ack_c(m - 1, ack_c(m, n - 1)); }