Rich Hickey らは、Clojure がinvokeDynamic
JVM 7 または 8 で予定されている次のバージョンから大幅に改善されることはないと述べていますが、末尾再帰によるパフォーマンスの向上は見られるでしょう。
末尾再帰は影響しますか
(fn [...] (recur ...))
また
(loop [...] (recur ...))
コンパイラはおそらくすでにループ構造を生成しているので、これ以上速くなるとは思いません。
Rich Hickey らは、Clojure がinvokeDynamic
JVM 7 または 8 で予定されている次のバージョンから大幅に改善されることはないと述べていますが、末尾再帰によるパフォーマンスの向上は見られるでしょう。
末尾再帰は影響しますか
(fn [...] (recur ...))
また
(loop [...] (recur ...))
コンパイラはおそらくすでにループ構造を生成しているので、これ以上速くなるとは思いません。
recur
使用する場合、テール再帰関数があることをコンパイラーにすでに伝えているため、サンプルは速くなりません。これにより、コンパイラーはgoto
(通常の命令ループのように)使用するバイトコードを生成できます。
もちろん、JVM がテール コールの最適化を取得すると、いくつかの利点があります。
もう recur を使用する必要はないので (使いたくない場合)、このような関数 (末尾再帰関数) を書くことができます。
(defn testfn [n] (when (not= 1000 n) (testfn n)))
現在、JVM は末尾再帰を検出できません。末尾呼び出しの最適化を追加することで、JVM は、これを記述したかのように上記の関数を認識できます (したがって、命令型のループ速度が得られます)。
(defn testfn [n] (when (not= 1000 n) (recur n)))
それほど大きな改善ではありませんが、テール コールの最適化が本当に優れている別のケースがあります。
相互に呼び出す関数 (場合によっては 2 つ以上) があり、スタックを保持する必要がない (末尾再帰的) 場合、JVM はそれらを最適化できます。recur
他の関数にジャンプするように指示できないため、これは現在不可能です。ここに例があります。
(defn even [n] (if (zero? n) true (odd? (dec n)))
(defn odd [n] (if (zero? n) false (even (dec n)))
大きな数でこれを試すと、スタックを吹き飛ばすことがわかっていますが、テールコールの最適化ではそうはなりません。
少し追加が残っています。trampoline
すでにこれを実行できるようにする関数が呼び出されています (プログラミング スタイルが少し変更され、オーバーヘッドが多少あります)。説明する代わりにtrampoline
、まさにそれを行うブログを紹介します。