Python 在自己的函数中使用所有索引优化列表理解性能

Python 在自己的函数中使用所有索引优化列表理解性能,python,numpy,numpy-ndarray,Python,Numpy,Numpy Ndarray,我有一个多维矩阵(6D),我需要迭代以生成一个新的6D矩阵。现在我使用列表理解来使代码尽可能干净,但是它确实很小。我希望有一些内置的numpy函数来帮助我,但是由于列表中使用了自己的函数,所以很难找到这样的函数 我已经尝试了np.fromIter,但这是错误的,因为我使用了多维列表。allReachableCoords(x1,y1,len(Q1),len(Q1[0])返回一组所有周围坐标({(x1,y1),(x1+1,y1),(x1,y1+1)…]),World.amountOfPossible

我有一个多维矩阵(6D),我需要迭代以生成一个新的6D矩阵。现在我使用列表理解来使代码尽可能干净,但是它确实很小。我希望有一些内置的numpy函数来帮助我,但是由于列表中使用了自己的函数,所以很难找到这样的函数

我已经尝试了np.fromIter,但这是错误的,因为我使用了多维列表。allReachableCoords(x1,y1,len(Q1),len(Q1[0])返回一组所有周围坐标({(x1,y1),(x1+1,y1),(x1,y1+1)…]),World.amountOfPossibleActions只返回5

算法从

Q1 = np.zeros((heightWorld, widthWorld, heightWorld, widthWorld, world.amountOfPossibleActions,
               world.amountOfPossibleActions))
然后将下面的过程重复几次

Q1 = np.array([[[[[[sum(
        world.joinedTransition((x1, y1), sf1, (x2, y2), sf2, action1, action2) *
        (world.joinedU((x1, y1), sf1, (x2, y2), sf2, action1, action2, player) +
         world.joinedU((x1, y1), sf1, (x2, y2), sf2, action2, action1, otherPlayer) +
         gamma * np.amax(Q1[sf1[1]][sf1[0]][sf2[1]][sf2[0]]))
        for sf1 in World.allReachableCoords(x1, y1, len(Q1), len(Q1[0]), world)
        for sf2 in World.allReachableCoords(x2, y2, len(Q1), len(Q1[0]), world)
    )
        for action1 in range(world.amountOfPossibleActions)]
        for action2 in range(world.amountOfPossibleActions)]
        for x1 in range(widthWorld)] for y1 in range(heightWorld)]
        for x2 in range(widthWorld)] for y2 in range(heightWorld)])
其中,连接的转换主要是一个if语句字符串:

# Transition function: Returns 0 if the final state is out of bounds, impassable terrain or too far from the
# initial state. If the given action explains the relation between si and sf return 1, otherwise 0.
def standardTransition(self, si, sf, a):
    if not (0 <= sf[0] <= len(self.grid[0]) and 0 <= sf[1] <= len(self.grid)):
        return 0
    if not (0 <= si[0] <= len(self.grid[0]) and 0 <= si[1] <= len(self.grid)):
        return 0
    if self.grid[sf[1]][sf[0]] == self.i or self.grid[si[1]][si[0]] == self.i:
        return 0
    if abs(si[0] - sf[0]) > 1 or abs(si[1] - sf[1]) > 1:
        return 0
    return {
        0: 1 if sf[0] == si[0] and sf[1] == si[1] else 0,  # Stay
        1: 1 if sf[0] == si[0] and sf[1] == si[1] + 1 else 0,  # Down
        2: 1 if sf[0] == si[0] and sf[1] == si[1] - 1 else 0,  # Up
        3: 1 if sf[0] == si[0] - 1 and sf[1] == si[1] else 0,  # Left
        4: 1 if sf[0] == si[0] + 1 and sf[1] == si[1] else 0  # Right
    }[a]

def joinedTransition(self, si1, sf1, si2, sf2, a1, a2):
    if sf1 == sf2: return 0  # Ending in the same square is impossible.
    if si1 == sf2 and si2 == sf1: return 0  # Going through each other is impossible.
    # Fighting for the same square.
    if si1 == sf1 and performAction(si1, a1) == sf2:  # Player 1 loses the fight
        return self.standardTransition(si1, sf2, a1) * self.standardTransition(si2, sf2,
                                                                               a2) * self.chanceForPlayer1ToWinDuel
    if si2 == sf2 and performAction(si2, a2) == sf1:  # Player 2 loses the fight
        return self.standardTransition(si1, sf1, a1) * self.standardTransition(si2, sf1, a2) * (
                1 - self.chanceForPlayer1ToWinDuel)
    return self.standardTransition(si1, sf1, a1) * self.standardTransition(si2, sf2, a2)
#Transition function:如果最终状态为越界、无法通行的地形或距离目标太远,则返回0
#初始状态。如果给定操作解释了si和sf之间的关系,则返回1,否则返回0。
def标准转换(自身、si、sf、a):
如果不是(0测量值:cProfile/line_profiler
加速一个程序的第一步应该始终是测量:你的时间到底花在了什么地方?总会有更快/更整洁的事情,但是如果速度是你主要关心的,那么你应该首先处理代码中最慢的部分

首先,您可以始终使用Python附带的默认探查器。要获得每行代码的更详细视图,我建议查看。虽然设置更复杂一些,但如果时间主要花在操作而不是函数上,它可以为您提供更好的结果

Timeit实验 考虑到我不知道您的代码的任何评测结果,我还注意到了一些其他的事情。在使用python的内置模块运行了一系列小实验之后,这里有一些明确的建议,可以让您的代码更快、更干净,或者两者兼而有之

Numpy索引 提高性能的初始方法可能只是更改索引。在numpy中对数组进行索引时,它似乎返回一个中间对象。因此,每一组新的方括号都是一个新的
\uuuuu getitem\uuuuu
函数调用,以及所有相关的开销。这意味着您的
Q1[v][w][x][y]
被(某种程度上)转换为

Q1.__getitem__(v).__getitem__(w).__getitem__(x).__getitem__(y)
Numpy本机支持,无需显式生成元组即可使用:

Q1[v][w][x][y]  # This is slow
Q1[(v,w,x,y)]   # This is faster
Q1[v,w,x,y]     # This does the same thing
通过使用后者,您已经可以节省为正在查找的项目编制索引所需时间的一半

$python -m timeit -s "import numpy as np; Q1 = np.empty((9,9,9,9,9,9)); sf=(3,4)" "q = Q1[sf[0]][sf[1]][sf[0]][sf[1]]"
1000000 loops, best of 3: 0.651 usec per loop

$python -m timeit -s "import numpy as np; Q1 = np.empty((9,9,9,9,9,9)); sf=(3,4)" "q = Q1[sf[0],sf[1],sf[0],sf[1]]"
1000000 loops, best of 3: 0.298 usec per loop
i、 e.将
np.amax(Q1[sf1[1]][sf1[0]][sf2[1]][sf2[0]])
替换为
np.amax(Q1[sf1[1],sf1[0],sf2[1],sf2[0]])

此外,您可以在for循环中解压
sf
变量(
对于sf1\u 0,sf1\u 1 in…
),从而节省另一段时间:

$python -m timeit -s "import numpy as np; Q1 = np.empty((9,9,9,9,9,9)); sf_0, sf_1=(3,4)" "q = Q1[sf_0,sf_1,sf_0,sf_1]"
1000000 loops, best of 3: 0.216 usec per loop
给出
np.amax(Q1[sf1\u 1,sf1\u 0,sf2\u 1,sf2\u 0])
,我认为这也有点干净:)

迭代:itertools.product 您当前正在手动/显式地在范围上循环,但实际上您只有最内层的循环中有一个计算。这意味着在它之外的五个循环中,您所做的只是创建
范围
对象并将其耗尽。就性能而言,这不是一个很大的瓶颈,但它不是很干净。幸运的是,内置的itertools library提供的工具正好可以执行以下任务:

# 6 nested loops
$python -m timeit -n 100 -s "a=0" "for u in range(10):" 
                                  "    for v in range(10):" 
                                  "        for w in range(10):" 
                                  "            for x in range(10):" 
                                  "                for y in range(10):" 
                                  "                    for z in range(10):" 
                                  "                        a+=1"
100 loops, best of 3: 57.3 msec per loop

# itertools.product
$python -m timeit -n 100 -s "from itertools import product; a=0" 
                            "for u,v,w,x,y,z in product(range(10),range(10),range(10),range(10),range(10),range(10)):" 
                            "    a+=1"
100 loops, best of 3: 55.6 msec per loop
在本例中,它节省了5个(!)级别的嵌套,甚至速度稍微快了一点。只要在前面创建一次
product()
并在以后使用它,就可以更快一点,因为您一直在重复相同的循环。只需确保显式地获取
list()
,如
product()
如果您尝试使用两次,将返回空的生成器(有关更多信息,请参阅例如)

缓存 在您的内部循环中,您还调用了
World
对象的一组方法。如果这些方法的结果根本不依赖于
Q1
,那么您肯定要对同一事物重新计算几次。然后您可以用计算时间换取内存:预先计算所有值一次,并将它们存储在另一个numpy数组中。an数组查找几乎肯定比函数调用的计算速度快(很多)

要决定首先在何处执行此操作,您应该参考分析工作的结果;)

Measure:cProfile/line\u profiler 加速计划的第一步应该始终是测量:你的时间到底花在哪里?总会有更快/更整洁的事情发生,但是如果速度是您主要关心的问题,那么您需要首先处理代码中最慢的部分

首先,您可以始终使用Python附带的默认探查器。对于每行代码的更详细的视图,我建议查看。虽然设置要复杂一些,但如果时间主要花在操作上而不是功能上,它可以给您带来更好的结果

Timeit实验 考虑到我不知道您的代码的任何评测结果,我还注意到了一些其他的事情。在使用python的内置模块进行了一系列小实验之后,这里有一些明确的建议,可以使代码更快、更干净,或者两者兼而有之

Numpy索引 提高性能的最初方法可能只是更改索引。在numpy中索引数组时,它似乎返回一个中间对象。因此,每一组新的方括号都是一个新的
\uuu getitem\uuu
函数调用,以及所有相关的开销。这意味着您的
Q1[v][w][x][y]
被(某种程度上)翻译为

Q1.__getitem__(v).__getitem__(w).__getitem__(x).__getitem__(y)
Numpy本机支持,无需显式生成元组即可使用:

Q1[v][w][x][y]  # This is slow
Q1[(v,w,x,y)]   # This is faster
Q1[v,w,x,y]     # This does the same thing
通过使用后者,您已经可以节省为正在查找的项目编制索引所需时间的一半

$python -m timeit -s "import numpy as np; Q1 = np.empty((9,9,9,9,9,9)); sf=(3,4)" "q = Q1[sf[0]][sf[1]][sf[0]][sf[1]]"
1000000 loops, best of 3: 0.651 usec per loop

$python -m timeit -s "import numpy as np; Q1 = np.empty((9,9,9,9,9,9)); sf=(3,4)" "q = Q1[sf[0],sf[1],sf[0],sf[1]]"
1000000 loops, best of 3: 0.298 usec per loop
i、 e.替换
np.amax(Q1[sf1[1]]s