且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《高阶Perl》——3.2 内联缓存

更新时间:2022-09-28 09:58:13

3.2 内联缓存

给一个函数添加缓存的最直接的方式就是给函数一个私有的散列。在这个例子里,可以使用一个数组代替散列,因为fib()的参数总是一个非负整数。但是一般需要使用一个散列,那么将会看到:

### Code Library: fib-cached
# Compute the number of pairs of rabbits alive in month n
{ my %cache;
  sub fib {
    my ($month) = @_;
    unless (exists $cache{$month}) {
      if ($month < 2) { $cache{$month} = $month }
      else {
        $cache{$month} = fib($month-1) + fib($month-2);
      }
    }
    return $cache{$month};
  }
}

这里的fib得到和之前一样的参数。但不是立即进入递归的Fibonacci数列计算,它先检查缓存。缓存是一个散列,%cache。当函数计算一个Fibonacci数fib($month),它将把这个值存入$cache{$month}。随后对fib()的调用将检查缓存散列中是否有这个值。这就是exists $cache{$month}测试的目的。如果缓存元素不存在,即函数此前没有为这个指定的$month调用过。在unless块中的代码就是一般的Fibonacci计算,包括如果需要的递归调用。然而,一旦函数算出了答案,它不是立即返回它,而是把数值插入缓存散列的合适位置。例如,当$month < 2是真,$cache{$month} = 1负责在缓存里占位。

在函数的末尾,return $cache{$month}返回缓存了的值,不管这个值是函数刚才插入的或者一开始就在那里的。

有了这些改变,fib函数变快了。在第1章看到的过度递归的问题简单地消失了。问题是由结果的重复的再次计算引起的,添加了缓存行为阻止了任何再次计算的出现。当函数尝试再次计算一个已经被计算过的结果时,它会立即从缓存里得到值。

3.2.1 静态变量

为什么%cache在fib的外面而不是里面,为什么有一个空的花括号包住%cache和fib?

如果%cache是在fib里面声明的,如下:

sub fib {
  my %cache;
  ...
}

那么缓存不会工作的,因为每次调用fib时,就会生成一个新的%cache变量,并在fib返回时抛弃。通过在任何函数的外面声明%cache,告诉Perl我们只想要一个%cache实例,它在程序首次编译时生成并且仅在程序结束时销毁。这就使得%cache可以积累数值并在调用fib前后保留它。一个类似%cache的在所有函数外部声明的变量称为一个静态变量(static variable),因为它的值保持不变,除非显式改变,也因为C语言的一个类似功能是由关键词static激活的。

%cache是由my声明的,所以它是词法域的。默认状态,它的作用域将持续到文件的结束。如果在fib后面定义了任何函数,它们也可以看到和修改缓存。但这不是我们想要的,我们想要缓存完全是fib私有的。把%cache和fib包在一个隔开的块中能达到这个目的。%cache的作用域仅延伸到此块的结束,这里面只有fib没别的了。