python - "Unrolling"递归函数?

标签 python recursion cuda opencl

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

我尝试手动展开函数,一个确定的模式(ddiffusee发射率):

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

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

最佳答案

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

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

尾递归变换

让我们再看看你的代码:

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

关于python - "Unrolling"递归函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6300695/

相关文章:

python - Python List Reverse 的时间复杂度是多少?

javascript - 最长公共(public)子序列(为什么这个递归解决方案不起作用?)

python - 求解仅给定几个变量的简单方程组

c - 我只看到主机的 "world hello"而不是设备

python - Rpy2 和 --max-ppsize

python - 如何从文本文件创建数据框

python - 如何将子列表转换为列表

javascript - 递归计算出现次数

c++ - 仅为从某种语言编译的对象添加链接库?

c++ - 由于 linux 中的 HRESULT 、 LARGE_INTEGER 和 DWORD 而出错