Как повысить производительность этого кода?
Благодаря некоторой помощи здешних людей я смог заставить свой код для головоломки с тасманскими верблюдами работать. Однако это ужасно медленно (я думаю. Я не уверен, потому что это моя первая программа на Python). Пример, выполняемый в нижней части кода, требует много времени для решения на моей машине:
dumrat@dumrat:~/programming/python$ time python camels.py
[['F', 'F', 'F', 'G', 'B', 'B', 'B'], ['F', 'F', 'G', 'F', 'B', 'B', 'B'],
['F', 'F', 'B', 'F', 'G', 'B', 'B'], ['F', 'F', 'B', 'F', 'B', 'G', 'B'],
['F', 'F', 'B', 'G', 'B', 'F', 'B'], ['F', 'G', 'B', 'F', 'B', 'F', 'B'],
['G', 'F', 'B', 'F', 'B', 'F', 'B'], ['B', 'F', 'G', 'F', 'B', 'F', 'B'],
['B', 'F', 'B', 'F', 'G', 'F', 'B'], ['B', 'F', 'B', 'F', 'B', 'F', 'G'],
['B', 'F', 'B', 'F', 'B', 'G', 'F'], ['B', 'F', 'B', 'G', 'B', 'F', 'F'],
['B', 'G', 'B', 'F', 'B', 'F', 'F'], ['B', 'B', 'G', 'F', 'B', 'F', 'F'],
['B', 'B', 'B', 'F', 'G', 'F', 'F']]
real 0m20.883s
user 0m20.549s
sys 0m0.020s
Вот код:
import Queue
fCamel = 'F'
bCamel = 'B'
gap = 'G'
def solution(formation):
return len([i for i in formation[formation.index(fCamel) + 1:]
if i == bCamel]) == 0
def heuristic(formation):
fCamels, score = 0, 0
for i in formation:
if i == fCamel:
fCamels += 1;
elif i == bCamel:
score += fCamels;
else:
pass
return score
def getneighbors (formation):
igap = formation.index(gap)
res = []
# AB_CD --> A_BCD | ABC_D | B_ACD | ABD_C
def genn(i,j):
temp = list(formation)
temp[i], temp[j] = temp[j], temp[i]
res.append(temp)
if(igap > 0):
genn(igap, igap-1)
if(igap > 1):
genn(igap, igap-2)
if igap < len(formation) - 1:
genn(igap, igap+1)
if igap < len(formation) - 2:
genn(igap, igap+2)
return res
class node:
def __init__(self, a, g, p):
self.arrangement = a
self.g = g
self.parent = p
def astar (formation, heuristicf, solutionf, genneighbors):
openlist = Queue.PriorityQueue()
openlist.put((heuristicf(formation), node(formation, 0, None)))
closedlist = []
while 1:
try:
f, current = openlist.get()
except IndexError:
current = None
if current is None:
print "No solution found"
return None;
if solutionf(current.arrangement):
path = []
cp = current
while cp != None:
path.append(cp.arrangement)
cp = cp.parent
path.reverse()
return path
#arr = current.arrangement
closedlist.append(current)
neighbors = genneighbors(current.arrangement)
for neighbor in neighbors:
if neighbor in closedlist:
pass
else:
openlist.put((current.g + heuristicf(neighbor),
node(neighbor, current.g + 1, current)))
#sorted(openlist, cmp = lambda x, y : x.f > y.f)
def solve(formation):
return astar(formation, heuristic, solution, getneighbors)
print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
#print solve([fCamel, fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel, bCamel])
Это только для 3 верблюдов каждый. Я хотел сделать это как минимум для 4. Этот тестовый пример все еще выполняется (прошло около 5 минут : (). Я обновлю это, если и когда это завершится.
Что мне следует сделать, чтобы улучшить этот код? (В основном с точки зрения производительности, но также приветствуются любые другие предложения).
Переведено автоматически
Ответ 1
Сначала позвольте мне рассказать вам, как найти проблему. Затем я расскажу вам, в чем она заключается.:
Я даже не потрудился попытаться разобраться в вашем коде. Я просто запустил его и взял 3 выборки стека в произвольное время. Я сделал это, набрав control-C и посмотрев на результирующую трассировку стека.
Один из способов взглянуть на это таков: если оператор появляется в X% случайных трассировок стека, то он находится в стеке примерно X% времени, так что это то, за что он отвечает. Если бы вы могли избежать его выполнения, именно столько вы бы сэкономили.
Хорошо, я взял 3 образца стека. Вот они:
File "camels.py", line 87, in <module>
print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
File "camels.py", line 85, in solve
return astar(formation, heuristic, solution, getneighbors)
File "camels.py", line 80, in astar
openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))
File "camels.py", line 87, in <module>
print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
File "camels.py", line 85, in solve
return astar(formation, heuristic, solution, getneighbors)
File "camels.py", line 80, in astar
openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))
File "camels.py", line 87, in <module>
print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
File "camels.py", line 85, in solve
return astar(formation, heuristic, solution, getneighbors)
File "camels.py", line 80, in astar
openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))
Обратите внимание, в этом случае все образцы стека идентичны. Другими словами, каждая из этих трех строк отвечает по отдельности почти за все время. Итак, посмотрите на них:
line 87: print solve([fCamel, fCamel, fCamel, gap, bCamel, bCamel, bCamel])
line solve: 85: return astar(formation, heuristic, solution, getneighbors)
line astar: 80: openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))
Очевидно, что строка 87 - это не та, выполнения которой можно избежать, и, вероятно, 85 тоже. Остается 80, вызов openlist.put
. Теперь вы не можете сказать, в +
операторе, heuristicf
вызове, node
call, или в put
вызове. Вы могли бы узнать, можно ли разделить их на отдельные строки.
Итак, я надеюсь, что вы узнаете из этого быстрый и простой способ выяснить, в чем заключаются ваши проблемы с производительностью.
Ответ 2
Я тоже сталкивался с этим раньше. На самом деле узким местом здесь является if neighbor in closedlist
.
Оператор in
настолько прост в использовании, что вы забываете, что это линейный поиск, а когда вы выполняете линейный поиск по спискам, он может быстро складываться. Что вы можете сделать, так это преобразовать closedlist в set
объект. При этом сохраняются хэши его элементов, поэтому in
оператор намного эффективнее, чем для списков. Однако списки не являются элементами, которые можно хэшировать, поэтому вам придется преобразовать свои конфигурации в кортежи, а не в списки.
Если порядок closedlist
имеет решающее значение для алгоритма, вы могли бы использовать set для in
оператора и вести параллельный список для ваших результатов.
Я попробовал простую реализацию этого, включая трюк namedtuple от aaronasterling, и он был выполнен за 0,2 секунды для вашего первого примера и 2,1 секунды для вашего второго, но я не пробовал проверять результаты для второго, более длинного примера.
Ответ 3
tkerwin прав в том, что вы должны использовать set для closedlist , что значительно ускоряет работу, но это все еще немного медленно для 4 верблюдов с каждой стороны. Следующая проблема заключается в том, что вы допускаете множество решений, которые невозможны, потому что вы разрешаете fCamels возвращаться назад, а bCamels двигаться вперед. Чтобы исправить это, замените строки,
if(igap > 0):
genn(igap, igap-1)
if(igap > 1):
genn(igap, igap-2)
if igap < len(formation) - 1:
genn(igap, igap+1)
if igap < len(formation) - 2:
genn(igap, igap+2)
с помощью
if(igap > 0 and formation[igap-1] == fCamel):
genn(igap, igap-1)
if(igap > 1 and formation[igap-2] == fCamel):
genn(igap, igap-2)
if (igap < len(formation) - 1) and formation[igap+1] == bCamel:
genn(igap, igap+1)
if (igap < len(formation) - 2) and formation[igap + 2] == bCamel:
genn(igap, igap+2)
затем я получаю решение проблемы с 4 верблюдами с каждой стороны примерно за 0,05 секунды, а не за 10 секунд. Я также попробовал использовать по 5 верблюдов с каждой стороны, и это заняло 0,09 секунды. Я также использую набор для closedlist и heapq, а не Queue.
Дополнительное ускорение
Вы можете получить дополнительное ускорение, правильно используя свою эвристику. В настоящее время вы используете строку
openlist.put((current.g + heuristicf(neighbor), node(neighbor, current.g + 1, current)))
(или его версия heapq), но вам следует изменить ее на
openlist.put((heuristicf(neighbor), node(neighbor, current.g + 1, current)))
Это не влияет на количество необходимых ходов, но это нормально. С этой головоломкой (и отсевом ходов, которые двигают верблюдов в неправильном направлении) вам не нужно беспокоиться о количестве необходимых ходов - либо ход приближает вас к решению, либо он заходит в тупик. Другими словами, все возможные решения требуют одинакового количества ходов. Одно это изменение отнимает время на поиск решения из 12 верблюдов в каждом боковом случае от более чем 13 секунд (даже при использовании heapq, установленного для closedlist, и изменений для поиска соседей выше) до 0,389 секунды. Это неплохо.
Кстати, лучший способ узнать, нашли ли вы решение, - это проверить, равен ли индекс первого fCamel длине формирования / 2 + 1 (используя int division) и равен ли индекс перед этим промежутку.
Ответ 4
Замена
class node:
def __init__(self, a, g, p):
self.arrangement = a
self.g = g
self.parent = p
с помощью
node = collections.namedtuple('node', 'arrangement, g, parent')
время сократилось примерно с 340-600 мс до 11.4 Время ввода 1,891 мс [fCamel, fCamel, gap, bCamel, bCamel]
. Результат тот же.
Очевидно, что это не поможет с какими-либо алгоритмическими проблемами, но что касается микрооптимизации, то это неплохо.
1 У меня были неправильные входные данные. Там было дополнительное fCamel
, из-за которого он работал медленнее.