【问题标题】:"Unrolling" a recursive function?“展开”递归函数?
【发布时间】:2011-09-12 03:25:52
【问题描述】:

我正在用 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)

我尝试手动展开函数,有一个明确的模式(ddiffuseeemittance):

Level 1:  d1 * e1 

Level 2:  d1 * d2 * e2
        + e1

Level 3:  d1 * d2 * d3 * e3
        + d1 * d2 * e2
        + e1

Level 4:  d1 * d2 * d3 * d4 * e4
        + d1 * d2 * d3 * e3
        + d1 * d2 * e2
        + e1

虽然我可能是错的......

我的问题是,我将如何在 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

我以前从未真正处理过解开递归函数的任务,因此非常感谢任何帮助。谢谢!

【问题讨论】:

  • 现在这个函数不是尾递归的。您将需要使用堆栈或使其尾递归。 (函数调用中有一个隐式堆栈。)
  • 问题:hit是什么?
  • hitray 所经过的对象。每次都不一样。
  • 但是hit 在整个递归过程中是不变的?或者即使在递归的跟踪中它也会改变?
  • 是的,每次迭代都会重新定义。这发生在我在第一个代码块中省略的代码中。我将对其进行编辑以使其明确...

标签: python recursion cuda opencl


【解决方案1】:

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

此答案适用于此示例的转换步骤。您可以将相同的方法应用于其他循环。

尾递归变换

让我们再看看你的代码:

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))。我们可以利用加法的分配律和乘法和加法的结合律将计算转换为[d1 * e1] + [(d1 * d2) * e2] + [(d1 * d2) * d3) * e3] + ... 。请注意,本系列中的第一项仅指迭代 1,第二项仅指迭代 1 和 2,依此类推。这告诉我们,我们可以即时计算这个系列。此外,本系列包含系列(d1, d1*d2, d1*d2*d3, ...),我们也可以即时计算。将其放回代码中:

def Trace(diffuse, emittance, ray):
  # 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]
  return Trace(new_diffuse, new_emittance, ray)

尾递归消除

在新循环中,被调用者完成后,调用者无事可做;它只是返回被调用者的结果。调用者没有工作要完成,所以它不必保存任何状态!代替调用,我们可以覆盖旧参数并返回到函数的开头(不是有效的 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

【讨论】:

【解决方案2】:

通常你总是可以用堆栈来表示递归。

例如:

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

本质上,递归的工作原理是将函数的参数压入堆栈并使用新参数再次调用该函数。您只需要使用堆栈来模拟它。

【讨论】:

  • 嗯,我有点困惑。 stack 包含hit(来自.pop())、颜色值? hit 是一个在每次while 循环迭代时随机变化的对象(ray 从它反射出来并可能撞到另一个对象,然后变成hit)。
  • 好的。我对不同的光线和颜色参数感到困惑。我更新了答案。
【解决方案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

【讨论】:

  • 原代码没有使用尾递归。它在递归调用之后执行加法和乘法。您可以使其尾递归,但这涉及一些编译器可能不会做出的重要假设。由于问题是关于如何消除递归,我认为重要的是不要将这些假设扫除。
  • 好的,我同意这不是真正的尾递归,但是这样对待它并且它应该仍然有效。
猜你喜欢
  • 2020-11-09
  • 2020-12-07
  • 1970-01-01
  • 2023-04-06
  • 2012-11-18
  • 2015-02-16
  • 1970-01-01
  • 2017-09-13
  • 2020-07-28
相关资源
最近更新 更多