如何提高此代码的性能?

时间:2010-11-28 07:27:43

标签: python performance optimization time-complexity

感谢来自这里的人们的帮助,我能够获得塔斯马尼亚骆驼拼图工作的代码。然而,它非常慢(我想。我不确定,因为这是我在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分钟:()。如果完成,我会更新它。

我该怎么做才能改进这段代码? (主要是表现方面,但欢迎任何其他建议)。

7 个答案:

答案 0 :(得分:58)

首先让我告诉你如何找到问题。然后我会告诉你它在哪里:

我甚至不打算试图找出你的代码。我刚刚运行它并采集了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来电还是put来电中。您可以找出是否可以将它们分成不同的行。

所以我希望你能从中找到一个快速简便的方法来找出你的性能问题。

答案 1 :(得分:38)

我之前也被这个绊倒了。这里的瓶颈实际上是if neighbor in closedlist

in语句非常易于使用,您忘记它是线性搜索,当您在列表上进行线性搜索时,它可以快速加起来。您可以做的是将closedlist转换为set对象。这会保留其项目的哈希值,因此in运算符比列表更有效。但是,列表不是可清除的项目,因此您必须将配置更改为元组而不是列表。

如果closedlist的顺序对算法至关重要,您可以使用in运算符的集合并为结果保留并行列表。

我尝试了一个简单的实现,包括aaronasterling的namedtuple技巧,它在你的第一个例子中以0.2秒执行,第二个例子在2.1秒内执行,但我没有尝试验证第二个更长的结果。

答案 2 :(得分:9)

tkerwin是正确的,你应该使用一个关闭列表的集合,这会加快速度,但对于每一侧的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个骆驼的解决方案,比如.05秒而不是10秒。我也试过每边5只骆驼,花了0.09秒。我也使用了一个关闭列表和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)))

这不会影响所需的移动次数,但这没关系。有了这个谜题(以及移动骆驼向错误方向移动的移动),你不需要担心它所采取的移动次数 - 要么是一个移动使你前进到解决方案,要么它将走向死胡同。换句话说,所有可能的解决方案都需要相同数量的移动。这一次更改花费时间从超过13秒(甚至使用heapq,设置为closedlist,以及更改以找到上面的邻居)找到每个侧面情况下的12只骆驼的解决方案为0.389秒。那不错。

顺便说一句,找到你是否找到解决方案的更好方法是检查第一个fCamel的索引是否等于阵型的长度/ 2 + 1(使用int除法)和索引在那之前等于差距。

答案 3 :(得分: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')

在输入[fCamel, fCamel, gap, bCamel, bCamel]上将时间从大约340-600毫秒下降到 11.4 1.89 1 msecs。它产生了相同的输出。

这显然无法解决任何算法问题,但就微观优化而言,它还不错。

1我的输入错误。有一个额外的fCamel让它运行得更慢。

答案 4 :(得分:3)

以下代码使用不到1秒来解决此问题:

from itertools import permutations

GAP='G'
LEFT='F'
RIGHT='B'
BEGIN=('F','F','F','F','G','B','B','B','B')
END=('B','B','B','B','G','F','F','F','F')
LENGTH=len(BEGIN)

ALL=set(permutations(BEGIN))

def NextMove(seq):
    g=seq.index(GAP)
    ret = set()
    def swap(n):
        return seq[:n]+seq[n:n+2][::-1]+seq[n+2:]
    if g>0 and seq[g-1]==LEFT:
        ret.add(swap(g-1))
    if g<LENGTH-1 and seq[g+1]==RIGHT:
        ret.add(swap(g))
    if g<LENGTH-2 and seq[g+1]==LEFT and seq[g+2]==RIGHT:
        ret.add(seq[:g]+seq[g+2:g+3]+seq[g+1:g+2]+seq[g:g+1]+seq[g+3:])
    if g>1 and seq[g-1]==RIGHT and seq[g-2]==LEFT:
        ret.add(seq[:g-2]+seq[g:g+1]+seq[g-1:g]+seq[g-2:g-1]+seq[g+1:])

    return ret

AllMoves={state:NextMove(state) for state in ALL}

def Solve(now,target):
    if now==target:
        return True
    for state in AllMoves[now]:
        if Solve(state,target):
            print(now)
            return True
    return False

Solve(BEGIN,END)

答案 5 :(得分:3)

好吧,我真的不能说你的算法在哪里误入歧途,但我只是继续做了我自己的算法。为了做最可能工作的最简单的事情,我使用了Dijkstra算法的混合版本,其中以任意顺序访问开放节点,而不考虑距离。这意味着我不必提出启发式方法。

""" notation: a game state is a string containing angle
    brackets ('>' and '<') and blanks
 '>>> <<<'

 """

def get_moves(game):
    result = []
    lg = len(game)
    for i in range(lg):
        if game[i] == '>':
            if i < lg-1 and game[i+1] == ' ': # '> ' -> ' >'
                result.append(game[:i]+' >'+game[i+2:])
            if i < lg-2 and game[i+1] != ' ' and game[i+2] == ' ': # '>< ' -> ' <>'
                result.append(game[:i]+' '+game[i+1]+'>'+game[i+3:])
        if game[i] == '<':
            if i >= 1 and game[i-1] == ' ': # ' <' -> '< '
                result.append(game[:i-1]+'< '+game[i+1:])
            if i >= 2 and game[i-1] != ' ' and game[i-2] == ' ': # ' ><' -> '<> '
                result.append(game[:i-2]+'<'+game[i-1]+' '+game[i+1:])
    return result



def wander(start, stop):
    fringe = [start]
    paths = {}

    paths[start] = ()

    def visit(state):
      path = paths[state]
      moves = [move for move in get_moves(state) if move not in paths]
      for move in moves:
          paths[move] = paths[state] + (state,)
      fringe.extend(moves)

    while stop not in paths:
      visit(fringe.pop())

    print "still open: ", len(fringe)
    print "area covered: " , len(paths)
    return paths[stop] + (stop,)

if __name__ == "__main__":
    start = '>>>> <<<<'
    stop = '<<<< >>>>'
    print start, "   -->   ", stop
    pathway = wander(start,stop)
    print len(pathway), "moves: ", pathway

答案 6 :(得分:0)

我的另一个答案是相当长的,所以我决定将此列为单独的答案。这个问题更适合进行深度优先搜索。我做了一个深度优先的搜索解决方案,它比我在其他答案中概述的更改(比OP代码快得多)的优化A-star方法快得多。例如,以下是在每侧17个骆驼上运行我的A-star和我的深度优先搜索方法的结果。

A-star:  14.76 seconds
Depth-first search: 1.30 seconds

如果您感兴趣,这是我的深度优先方法代码:

from sys import argv

fCamel = 'F'
bCamel = 'B'
gap = 'G'

def issolution(formlen):
    def solution(formation):
        if formation[formlen2] == gap:
            return formation.index(fCamel) == x
        return 0
    x = formlen/2 + 1
    formlen2 = formlen/2
    return solution

def solve(formation):
    def depthfirst(form, g):
        if checksolution(form):
            return [tuple(form)], g + 1
        else:
            igap = form.index(gap)
            if(igap > 1 and form[igap-2] == fCamel):
                form[igap-2],form[igap] = form[igap],form[igap-2]
                res = depthfirst(form,g+1)
                form[igap-2],form[igap] = form[igap],form[igap-2]
                if res != 0:
                    return [tuple(form)]+res[0],res[1]
            if (igap < flen - 2) and form[igap + 2] == bCamel:
                form[igap+2],form[igap] = form[igap],form[igap+2]
                res = depthfirst(form,g+1)
                form[igap+2],form[igap] = form[igap],form[igap+2]
                if res != 0:
                    return [tuple(form)]+res[0],res[1]
            if(igap > 0 and form[igap-1] == fCamel):                
                form[igap-1],form[igap] = form[igap],form[igap-1]
                res = depthfirst(form,g+1)
                form[igap-1],form[igap] = form[igap],form[igap-1]
                if res != 0:
                    return [tuple(form)]+res[0],res[1]               
            if (igap < flen - 1) and form[igap+1] == bCamel:
                form[igap+1],form[igap] = form[igap],form[igap+1]
                res = depthfirst(form,g+1)
                form[igap+1],form[igap] = form[igap],form[igap+1]
                if res != 0:
                    return [tuple(form)]+res[0],res[1]                
            return 0
    flen = len(formation)
    checksolution = issolution(flen)
    res = depthfirst(list(formation), 0)
    return res

L = lambda x: tuple([fCamel]*x + [gap] + [bCamel]*x)
print solve(L(int(argv[1])))