“展开”递归函数?

2024-09-29 01:25:28 发布

您现在位置:Python中文网/ 问答频道 /正文

我在C++中编写了一个路径跟踪程序,我想尝试将最资源密集的代码实现到CUDA或OpenCL(我不知道选哪个)。在

我听说我的显卡版本的CUDA不支持递归,这是我的路径跟踪器大量使用的东西。在

< >我在Python和C++中都对它进行了编码,我会发布一些简化的Python代码,以便于阅读:

def Trace(ray):
  hit = what_object_is_hit(ray)

  if not hit:
    return Color(0, 0, 0)

  newRay = hit.bouceChildRayOffSurface(ray)

  return hit.diffuse * (Trace(newRay) + hit.emittance)

我尝试手动展开函数,结果是一个确定的模式(ddiffuse,而{}是{}):

^{pr2}$

不过,我可能错了。。。在

我的问题是,如何在while循环中实现这段代码?在

我想用这种形式:

total = Color(0, 0, 0)
n = 1

while n < 10:   # Maximum recursion depth
  result = magical_function()

  if not result:  break

  total += result
  n += 1

我以前从来没有真正处理过分解递归函数的任务,所以任何帮助都将不胜感激。谢谢!在


Tags: 代码路径returniftracenotresultcuda
3条回答

你真走运。您的代码使用尾部递归,也就是说,当您在函数中使用递归时。编译器通常可以为您执行此操作,但您必须在此处手动执行:

total = Color(0, 0, 0)
mult = 1
n = 1

while n < 10:   # Maximum recursion depth
  # Here was code to look for intersections

  if not hit: break

  total += mult * hit.diffuse * hit.emittance
  mult *= hit.diffuse
  n += 1

return total

通常,您可以始终使用堆栈来表示递归。在

例如:

stack.push(Color(0,0,0), ray, 0) // color, ray, level#
while (!stack.empty()):
   current = stack.pop()
   if (current.level == 10): break
   // compute hit, and newray from current.ray
   stack.push(hit.diffuse*(current.color + hit.emittance), newray, current.level+1)
return current

本质上,递归的工作原理是将函数的参数推送到堆栈上,然后用新的参数再次调用函数。你只需要使用堆栈来模拟这个。在

在递归函数中,每次递归调用发生时,调用方的状态都被保存到堆栈中,然后在递归调用完成时恢复。要将递归函数转换为迭代函数,需要将挂起函数的状态转换为显式数据结构。当然,您可以在软件中创建自己的堆栈,但通常可以使用一些技巧来提高代码的效率。在

这个答案贯穿于本例的转换步骤。您可以对其他循环应用相同的方法。在

尾递归变换

让我们再看看您的代码:

def Trace(ray):
  # Here was code to look for intersections

  if not hit:
      return Color(0, 0, 0)

  return hit.diffuse * (Trace(ray) + hit.emittance)

一般来说,递归调用必须返回到调用函数,这样调用者才能完成它所做的工作。在这种情况下,调用者通过执行加法和乘法来“完成”。这会产生一个类似 d1 * (d2 * (d3 * (... + e3) + e2) + e1))。我们可以利用加法的分配律和乘法与加法的结合律将计算转化为{}。请注意,本系列中的第一项仅指迭代1,第二项仅指迭代1和2,依此类推。这告诉我们我们可以动态地计算这个级数。此外,这个系列包含序列(d1, d1*d2, d1*d2*d3, ...),我们也可以动态计算。把它放回代码里:

^{pr2}$

尾递归消除

在新循环中,调用者在被调用者完成后没有工作要做;它只是返回被调用者的结果。调用者没有工作要完成,所以它不必保存任何状态!代替调用,我们可以覆盖旧参数并返回函数的开头(不是有效的Python,但它说明了这一点):

def Trace(diffuse, emittance, ray):
  beginning:
  # Here was code to look for intersections

  if not hit: return emittance                            # The complete value has been computed

  new_diffuse = diffuse * hit.diffuse                     # (...) * dN
  new_emittance = emittance + new_diffuse * hit.emittance # (...) + [(d1 * ... * dN) + eN]
  (diffuse, emittance) = (new_diffuse, new_emittance)
  goto beginning

最后,我们将递归函数转换成一个等价循环。剩下的就是用Python语法来表达它。在

def Trace(diffuse, emittance, ray):
  while True:
    # Here was code to look for intersections

    if not hit: break

    diffuse = diffuse * hit.diffuse                 # (...) * dN
    emittance = emittance + diffuse * hit.emittance # (...) + [(d1 * ... * dN) + eN]

  return emittance

相关问题 更多 >