为什么低效的因子计算是......高效(和快速)?

时间:2016-04-20 07:14:17

标签: c++ recursion memoization

我编写了这个简单的程序来测试memoization技术:

int main() {
    function<double(double)> f = [&f](double i) -> double {
        if (i == 1)
            return 1;
        else
            return i * f(i - 1);
    };
    cout << f(100) << endl;
}

我原本期望在几秒钟内执行此代码(因为它的递归效率很低),但实际上花了几毫秒......为什么?我认为在引擎盖下有一些编译器优化,但我不知道会发生什么。

奖金问题: 你能给我一个简单的程序,它执行效率低(编译器优化与否),所以我可以测试memoization的好处吗?

2 个答案:

答案 0 :(得分:2)

记忆技术旨在应用于优化昂贵的函数调用。因子函数不是这种情况。 C ++非常快,因此阶乘函数调用永远不会超过几毫秒计算。 (至少如果不使用多精度)。 factorial(100)是“仅”100次乘法,因此C ++没有任何内容。

如果这仅用于测试或演示目的,我只会在函数调用中引入延迟(睡眠,长虚拟循环或其他)。 随着记忆的实施,这种延迟不应该发生,所以它几乎“几乎”没时间运行。

这是我要做的一个例子。 factorial 是一项昂贵的功能。 memo_factorial 是实现memoization技术的包装。 在第一次调用函数时,输入和输出字典被更新,在以下使用相同输入的调用中,先前存储的值将返回,因此“real”函数不会再次执行。

#define ELAPSE(cmd) { clock_t s = clock();\
    long ret = cmd;\
    cout << "\t" << #cmd\
         << " = " << ret \
         << "\t(" << (clock()-s)/double(CLOCKS_PER_SEC) << " secs)" \
         << endl; }

long factorial(long i) {
    for(clock_t s = clock(); (clock()-s)<CLOCKS_PER_SEC; );
    return i<=1 ? 1 : i*factorial(i-1);
}
long memo_factorial(long i) {
    static map<long,long> saved;
    map<long,long>::const_iterator it = saved.find(i);
    return ( it==saved.end() ) ? (saved[i] = memo_factorial(i)) : it->second;
}

int main() {
    cout << "first execution WITHOUT memoization" << endl;
    for(int i=1; i<5; ++i) {
        ELAPSE( memo_factorial(i) )
    }

    cout << "second execution WITH memoization" << endl;
    for(int i=1; i<5; ++i) {
        ELAPSE( memo_factorial(i) )
    }

    return 0;
}

输出应为:

first execution WITHOUT memoization
    memo_factorial(i) = 1   (1 secs)
    memo_factorial(i) = 2   (1 secs)
    memo_factorial(i) = 6   (1 secs)
    memo_factorial(i) = 24  (1 secs)
second execution WITH memoization
    memo_factorial(i) = 1   (0 secs)
    memo_factorial(i) = 2   (0 secs)
    memo_factorial(i) = 6   (0 secs)
    memo_factorial(i) = 24  (0 secs)

希望你觉得它很有用。

此致 亚历

注意:factorial通常在整数值上定义。当然,它只是一系列乘法因此可以应用于其他类型。

答案 1 :(得分:1)

Memoization主要是一种改进计算algorithmic complexity的技术,而不是避免递归。这就是为什么Fibonacci数字比使用阶乘函数更好的例子(即使WikiPedia页面使用阶乘函数)。

查看wikibook on dynamic programming中的数字。在第二个数字中划掉的所有电话都是您从记忆中获得的节省。使用阶乘函数,不会划掉任何东西。