У меня есть класс, который решает точную проблему покрытия, используя рекурсивный алгоритм обратного отслеживания. Первоначально я реализовал класс с функцией обратного вызова, которую передал объекту во время инициализации. Этот обратный вызов вызывается всякий раз, когда найдено решение. Рассматривая чью-либо реализацию той же проблемы, я увидел, что они использовали операторы yield для передачи решения, иными словами, их код был генератором Python. Я подумал, что это интересная идея, поэтому я сделал новую версию своего класса, чтобы использовать выходы. Затем я провел сравнительные тесты между двумя версиями и, к своему удивлению, обнаружил, что версия генератора работала в 5 раз медленнее, чем версия с обратным вызовом. Обратите внимание, что за исключением переключения доходности для обратного вызова, код идентичен.
Что здесь происходит? Я предполагаю, что поскольку генератору необходимо сохранить информацию о состоянии перед выдачей, а затем восстановить это состояние при перезапуске при следующем вызове, именно это сохранение / восстановление и делает версию генератора намного медленнее. Если это так, то сколько информации о состоянии требуется генератору сохранить и восстановить?
Есть идеи от экспертов по питону?
- отредактировано 7:40 PDT
Вот код решателя, который использует yield. Замените первый выход ниже приведенным вызовом функции обратного вызова и измените следующий цикл со вторым выходом просто на рекурсивный вызов для решения для исходной версии этого кода.
def solve(self):
for tp in self.pieces:
if self.inuse[tp.name]: continue
self.inuse[tp.name] = True
while tp.next_orientation() is not None:
if tp.insert_piece():
self.n_trials += 1
self.pieces_in += 1
self.free_cells -= tp.size
if self.pieces_in == len(self.pieces) or self.free_cells == 0:
self.solutions += 1
self.haveSolution = True
yield True
self.haveSolution = False
else:
self.table.next_base_square()
for tf in self.solve():
yield tf
tp.remove_piece()
self.pieces_in -= 1
self.table.set_base_square(tp.base_square)
self.free_cells += tp.size
self.inuse[tp.name] = False
tp.reset_orientation()
Почтовый цикл, который вызывает решатель (после инициализации, конечно):
start_time = time.time()
for tf in s.solve():
printit(s)
end_time = time.time()
delta_time = end_time - start_time
В версии с обратным вызовом цикл исчезает, и для его решения требуется всего один вызов.