为什么要在递归Postscript过程中使用保存/恢复?

时间:2018-09-03 06:12:39

标签: postscript

在格伦·里德(Glenn C. Reid)的著作《思考后记》(Thinking in Postscript,1990)中,两个版本 显示了一个递归函数。

该函数采用一个整数参数:如果参数为奇数, 它返回参数;如果参数是偶数,则函数 递归地调用自身并返回结果加一,因此结果始终是奇数。

Example 6.5: Recursion Using the Dictionary Stack

/recurse_proc % int recurse_proc int
{ %def
      save
      2 dict begin
              /save_obj exch def
              /arg exch def
              arg 2 div truncate
              2 mul cvi
              arg eq { %ifelse
                     % even number
                     arg 1 add recurse_proc
              }{ %else
                     arg
              } ifelse
              save_obj % leave on stack
      end
      restore % to save_obj on stack
} bind def
2 recurse_proc
     

示例6.6中的过程与示例6.5中的过程相同,但重写后使用了操作数堆栈而不是字典堆栈。

Example 6.6: Recursion Using the Operand Stack

/recurse_proc % int recurse_proc int
{ %def
      dup 2 div truncate
      2 mul cvi
      1 index eq { %ifelse
              % even number
              1 add recurse_proc
      } if
} bind def
2 recurse_proc

我的问题是:save / restore的意义是什么 在例6.5中?没有它,该程序就可以正常工作(如果也省略了save_obj操作),对吗? 省略它会使程序在某种程度上变得更糟吗?

给出的解释是:

  

在此示例中,字典分配的内存为   由saverestore回收,将每个保存对象放入递归中   直到需要它为止。如果以递归方式调用该函数,则更多   可能会生成一个保存对象,但每个对象最终都会   在递归调用返回时恢复。

我不明白。 begin / end是否足以回收需要回收的任何内存?我对save / restore的功能并没有很深入的了解,但是它们听起来像是重量级的操作,这使得它们在这里的出现显得更加奇怪。

安德烈·赫克(AndréHeck)的"Learning PostScript by Doing" (2005)对于其示例使用save / restore进行类似操作,其解释本质上是相同的。

2 个答案:

答案 0 :(得分:1)

实际上是使字典操作数成为字典堆栈上的当前字典。最后要做的就是从堆栈中删除字典。

因此'end'不会检查(例如)仍在词典堆栈中的任何词典是否都包括您刚刚为end调用的词典。它也不检查操作数堆栈以查看是否从那里引用了字典。等等

这意味着'end'无法确定不再引用该词典,因此它无法丢弃其正在使用的内存。

因此,这些操作均无法恢复任何内存。 PostScript使用垃圾收集内存模型,因此您无法确定何时恢复内存。但是,保存将“按原样”保存当前内存,而还原将内存恢复到该点。因此,无论在保存和还原之间发生什么情况,还原后的内存将与保存时的内存完全相同。这是唯一可以确定的方法。

垃圾回收器和保存/还原的精确动作未在该语言中定义,它的作用足以使其在执行运算符时表现出来。

我已经在PostScript中看到了以多种不同方式实现的内存处理,并且保存和还原的确切操作差异很大。但是,它们通常不是很“沉重”,因为从本质上讲,您只是在内存中标记要保存的内容,然后在还原时将所有内容扔掉。

另一方面,

vmreclaim通常调用标记/清除操作以检查VM中分配的所有对象,以查看它们是否仍被引用,否则将其丢弃。

因此,代替保存/还原,您可以(通常)用vmreclaim替换还原。效果大致相同,但是执行起来需要更长的时间。

答案 1 :(得分:0)

我认为您正确地感到困惑。我认为在第一个程序中,从语义的角度来看,saverestore是完全不必要的。唯一的有益效果(在1级解释程序上)是确保可以将用于创建字典的内存重新使用。 Postscript Level 2在1992年发布,距本书2年。他可能有提供垃圾收集的实验口译员。但是对于使用级别1的口译员的听众来说,他必须以级别1为目标,而saverestore是回收记忆的唯一方法。 >

格伦·里德(Glenn Reid)可能想举三个例子,但不知何故,他们两个被挤在一起了。看一下 with save / restore函数,但没有创建字典。

/recurse_proc % int recurse_proc int
{ %def
      save
          /save_obj exch def
          /arg exch def
          arg 2 div truncate
          2 mul cvi
          arg eq { %ifelse
                 % even number
                 arg 1 add recurse_proc
          }{ %else
                 arg
          } ifelse
          save_obj % leave on stack
      restore % to save_obj on stack
} bind def
2 recurse_proc

这仍然有效!可以说它是“使用保存堆栈” [*]。唯一的问题是局部变量具有全局可见性[**]。

感谢您提出一个很好的问题!顿悟,阅读问题后这是可能的。

[*] saverestore的确切实现不一定需要使用硬件堆栈,但是它们的操作在PLRM中被描述为使VM能够遵循“类似堆栈的规则” “。

[**]但是,奇怪的是,这似乎不是一个 real 问题,因为变量的生存期有限。即使在其他地方使用了名称save_objarg,对restore的调用也会将它们还原为原始状态。如果函数对字符串中的字节进行了任何修改,情况就不会如此,因为字符串值不受saverestore的影响。