ここでの答えのほとんどは間違っています。正解は、状況によって異なります。たとえば、ツリーをウォークスルーする2つのC関数を次に示します。最初に再帰的なもの:
static
void mm_scan_black(mm_rc *m, ptr p) {
SET_COL(p, COL_BLACK);
P_FOR_EACH_CHILD(p, {
INC_RC(p_child);
if (GET_COL(p_child) != COL_BLACK) {
mm_scan_black(m, p_child);
}
});
}
そして、これは反復を使用して実装された同じ関数です:
static
void mm_scan_black(mm_rc *m, ptr p) {
stack *st = m->black_stack;
SET_COL(p, COL_BLACK);
st_push(st, p);
while (st->used != 0) {
p = st_pop(st);
P_FOR_EACH_CHILD(p, {
INC_RC(p_child);
if (GET_COL(p_child) != COL_BLACK) {
SET_COL(p_child, COL_BLACK);
st_push(st, p_child);
}
});
}
}
コードの詳細を理解することは重要ではありません。それp
がノードであり、それP_FOR_EACH_CHILD
がウォーキングを行います。反復バージョンでは、st
ノードがプッシュされ、ポップされて操作される明示的なスタックが必要です。
再帰関数は、反復関数よりもはるかに高速に実行されます。その理由は、後者では、各アイテムCALL
に対して、関数へのa st_push
が必要であり、次に別のへの必要があるためですst_pop
。
前者では、CALL
各ノードの再帰のみが可能です。
さらに、コールスタック上の変数へのアクセスは非常に高速です。これは、常に最も内側のキャッシュにある可能性が高いメモリから読み取ることを意味します。一方、明示的なスタックは、malloc
アクセスがはるかに遅いヒープから:edメモリーにバッキングする必要があります。
インライン化st_push
やなどの慎重な最適化によりst_pop
、再帰的アプローチとほぼ同等に到達できます。しかし、少なくとも私のコンピューターでは、ヒープメモリにアクセスするコストは再帰呼び出しのコストよりも大きくなっています。
ただし、再帰的なツリーウォーキングは正しくないため、この説明はほとんど意味がありません。ツリーが十分に大きい場合は、コールスタックスペースが不足するため、反復アルゴリズムを使用する必要があります。