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

问题描述 投票:1回答:2

在Glenn C. Reid的书“Thinking in Postscript”(1990)中,显示了递归函数的两个版本。

该函数接受一个整数参数:如果参数为奇数,则返回参数;如果参数是偶数,则函数以递归方式调用自身并返回结果加1,因此结果始终为奇数。

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

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

给出的解释是:

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

我不明白。是不是begin / end足以回收任何需要回收的记忆?我对save / restore所做的事情没有非常深刻的理解,但它们听起来像是相当重量级的操作,这使得它们在这里看起来更加奇怪。

AndréHeck的"Learning PostScript by Doing" (2005)在其示例中使用了save / restore,其解释基本相同。

postscript
2个回答
1
投票

实际上,开始使字典操作数成为字典堆栈上的当前字典。所有结束都是从堆栈中删除字典。

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

这意味着'end'不能决定不再引用字典,因此它不能丢弃它正在使用的内存。

所以这些操作都没有恢复任何记忆。 PostScript使用Garbage Collected内存模型,因此无法确定何时恢复内存。但是,save会按原样保存当前内存并恢复将内存恢复到该点。因此,无论保存和恢复之间发生什么,恢复后的内存与保存时的内存完全相同。这是确定的唯一方法。

垃圾收集器和保存/恢复的精确操作没有在语言中定义,它足以使其执行操作符时所描述的行为。

我已经看到在PostScript中以多种不同的方式实现了内存处理,并且保存和恢复的确切操作差异很大。然而,它们通常不是非常“重量级”,因为基本上你只是在记忆中为保存做出标记,并在你执行恢复时将所有内容扔掉。

另一方面,vmreclaim通常调用标记/清除操作来检查VM中分配的所有对象,看它们是否仍然被引用,如果没有,则丢弃它们。

因此,您可以(通常)使用vmreclaim替换恢复,而不是保存/恢复。效果大致相同,但执行需要更长的时间。


0
投票

我认为你是对的。我认为在第一个程序中,从语义学的角度来看,saverestore是完全没有必要的。唯一有益的效果(在1级解释器上)是它确保用于创建字典的内存可用于重用。 Postscript Level 2于1992年发布,因此在本书发布2年后。他可能有实验性的口译员可以使用Garbage Collection。但对于那些使用已安装的1级口译员基础的观众,他必须将目标定为1级,saverestore是回收记忆的唯一方法。

Glenn Reid有可能打算做三个例子但不知何故其中两个被压扁了。使用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能够遵循“类似堆栈的规则”。

[**]但是,奇怪的是,这似乎不是一个真正的问题,因为变量的寿命有限。即使在其他地方使用了名称save_objarg,对restore的调用也会将它们恢复到原始状态。如果函数对字符串中的字节进行了任何修改,则不会出现这种情况,因为字符串值不受saverestore的影响。

© www.soinside.com 2019 - 2024. All rights reserved.