«Развернуть» рекурсивную функцию? - PullRequest
13 голосов
/ 10 июня 2011

Я пишу трассировщик пути в 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)

Я попытался вручную развернуть функцию, и там это определенный шаблон (d это diffuse и e это emittance):

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

Я никогда раньше не сталкивался с задачей раскрытия рекурсивной функции, поэтому любая помощь будет принята с благодарностью. Спасибо!

Ответы [ 3 ]

15 голосов
/ 10 июня 2011

В рекурсивной функции каждый раз, когда происходит рекурсивный вызов, состояние вызывающего абонента сохраняется в стеке, а затем восстанавливается, когда рекурсивный вызов завершен.Чтобы преобразовать рекурсивную функцию в итеративную, необходимо превратить состояние приостановленной функции в явную структуру данных.Конечно, вы можете создать свой собственный стек в программном обеспечении, но часто есть приемы, которые вы можете использовать для повышения эффективности своего кода.

Этот ответ прорабатывает шаги преобразования для этого примера.Вы можете применить те же методы к другим циклам.

Преобразование хвостовой рекурсии

Давайте еще раз посмотрим на ваш код:

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
3 голосов
/ 10 июня 2011

Вам повезло. Ваш код использует хвостовую рекурсию, то есть когда вы используете рекурсию как последнюю вещь в вашей функции. Компилятор обычно может сделать это за вас, но вам придется сделать это вручную здесь:

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
0 голосов
/ 10 июня 2011

Как правило, вы всегда можете представить рекурсию с стеком .

Например:

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

По существу рекурсия работает, помещая аргументы функции в стек и вызывая функцию снова с новыми аргументами. Вы просто должны эмулировать это, используя стек.

...