如何优化Python字典的这种使用?

8cdiaqws  于 2022-12-25  发布在  Python
关注(0)|答案(5)|浏览(149)
    • 问题:**

我已经把我的Python程序分析得要死了,有一个函数让所有的东西都慢了下来。它大量使用Python字典,所以我可能没有以最好的方式使用它们。如果我不能让它运行得更快,我将不得不用C++重写它,那么有人能帮我用Python优化它吗?
我希望我已经给出了正确的解释,希望你能理解我的代码!提前感谢你的帮助。

    • 我的密码:**

这是使用line_profiler and kernprof分析的违规函数。
我尤其对第363、389和405行感到困惑,在这几行中,比较两个变量的if语句似乎花费了过多的时间。
我考虑过使用NumPy(因为它处理稀疏矩阵),但我认为它不合适,因为:(1)我没有使用整数来索引矩阵(我使用的是对象示例);(2)我没有在矩阵中存储简单的数据类型(我存储的是一个浮点数和一个对象示例的元组),但是我愿意相信NumPy,如果有人知道NumPy的稀疏矩阵性能与Python的哈希表相比,我会很感兴趣。
对不起,我没有给你一个简单的例子,你可以运行,但这个函数是捆绑在一个更大的项目,我不能想出如何建立一个简单的例子来测试它,没有给你我的代码库的一半!

Timer unit: 3.33366e-10 s
File: routing_distances.py
Function: propagate_distances_node at line 328
Total time: 807.234 s

Line #   Hits         Time  Per Hit   % Time  Line Contents
328                                               @profile
329                                               def propagate_distances_node(self, node_a, cutoff_distance=200):
330                                                       
331                                                   # a makes sure its immediate neighbours are correctly in its distance table
332                                                   # because its immediate neighbours may change as binds/folding change
333    737753   3733642341   5060.8      0.2          for (node_b, neighbour_distance_b_a) in self.neighbours[node_a].iteritems():
334    512120   2077788924   4057.2      0.1              use_neighbour_link = False
335                                                       
336    512120   2465798454   4814.9      0.1              if(node_b not in self.node_distances[node_a]): # a doesn't know distance to b
337     15857     66075687   4167.0      0.0                  use_neighbour_link = True
338                                                       else: # a does know distance to b
339    496263   2390534838   4817.1      0.1                  (node_distance_b_a, next_node) = self.node_distances[node_a][node_b]
340    496263   2058112872   4147.2      0.1                  if(node_distance_b_a > neighbour_distance_b_a): # neighbour distance is shorter
341        81       331794   4096.2      0.0                      use_neighbour_link = True
342    496182   2665644192   5372.3      0.1                  elif((None == next_node) and (float('+inf') == neighbour_distance_b_a)): # direct route that has just broken
343        75       313623   4181.6      0.0                      use_neighbour_link = True
344                                                               
345    512120   1992514932   3890.7      0.1              if(use_neighbour_link):
346     16013     78149007   4880.3      0.0                  self.node_distances[node_a][node_b] = (neighbour_distance_b_a, None)
347     16013     83489949   5213.9      0.0                  self.nodes_changed.add(node_a)
348                                                           
349                                                           ## Affinity distances update
350     16013     86020794   5371.9      0.0                  if((node_a.type == Atom.BINDING_SITE) and (node_b.type == Atom.BINDING_SITE)):
351       164      3950487  24088.3      0.0                      self.add_affinityDistance(node_a, node_b, self.chemistry.affinity(node_a.data, node_b.data))     
352                                                   
353                                                   # a sends its table to all its immediate neighbours
354    737753   3549685140   4811.5      0.1          for (node_b, neighbour_distance_b_a) in self.neighbours[node_a].iteritems():
355    512120   2129343210   4157.9      0.1              node_b_changed = False
356                                               
357                                                       # b integrates a's distance table with its own
358    512120   2203821081   4303.3      0.1              node_b_chemical = node_b.chemical
359    512120   2409257898   4704.5      0.1              node_b_distances = node_b_chemical.node_distances[node_b]
360                                                       
361                                                       # For all b's routes (to c) that go to a first, update their distances
362  41756882 183992040153   4406.3      7.6              for node_c, (distance_b_c, node_after_b) in node_b_distances.iteritems(): # Think it's ok to modify items while iterating over them (just not insert/delete) (seems to work ok)
363  41244762 172425596985   4180.5      7.1                  if(node_after_b == node_a):
364                                                               
365  16673654  64255631616   3853.7      2.7                      try:
366  16673654  88781802534   5324.7      3.7                          distance_b_a_c = neighbour_distance_b_a + self.node_distances[node_a][node_c][0]
367    187083    929898684   4970.5      0.0                      except KeyError:
368    187083   1056787479   5648.8      0.0                          distance_b_a_c = float('+inf')
369                                                                   
370  16673654  69374705256   4160.7      2.9                      if(distance_b_c != distance_b_a_c): # a's distance to c has changed
371    710083   3136751361   4417.4      0.1                          node_b_distances[node_c] = (distance_b_a_c, node_a)
372    710083   2848845276   4012.0      0.1                          node_b_changed = True
373                                                                   
374                                                                   ## Affinity distances update
375    710083   3484577241   4907.3      0.1                          if((node_b.type == Atom.BINDING_SITE) and (node_c.type == Atom.BINDING_SITE)):
376     99592   1591029009  15975.5      0.1                              node_b_chemical.add_affinityDistance(node_b, node_c, self.chemistry.affinity(node_b.data, node_c.data))
377                                                                   
378                                                               # If distance got longer, then ask b's neighbours to update
379                                                               ## TODO: document this!
380  16673654  70998570837   4258.1      2.9                      if(distance_b_a_c > distance_b_c):
381                                                                   #for (node, neighbour_distance) in node_b_chemical.neighbours[node_b].iteritems():
382   1702852   7413182064   4353.4      0.3                          for node in node_b_chemical.neighbours[node_b]:
383   1204903   5912053272   4906.7      0.2                              node.chemical.nodes_changed.add(node)
384                                                       
385                                                       # Look for routes from a to c that are quicker than ones b knows already
386  42076729 184216680432   4378.1      7.6              for node_c, (distance_a_c, node_after_a) in self.node_distances[node_a].iteritems():
387                                                           
388  41564609 171150289218   4117.7      7.1                  node_b_update = False
389  41564609 172040284089   4139.1      7.1                  if(node_c == node_b): # a-b path
390    512120   2040112548   3983.7      0.1                      pass
391  41052489 169406668962   4126.6      7.0                  elif(node_after_a == node_b): # a-b-a-b path
392  16251407  63918804600   3933.1      2.6                      pass
393  24801082 101577038778   4095.7      4.2                  elif(node_c in node_b_distances): # b can already get to c
394  24004846 103404357180   4307.6      4.3                      (distance_b_c, node_after_b) = node_b_distances[node_c]
395  24004846 102717271836   4279.0      4.2                      if(node_after_b != node_a): # b doesn't already go to a first
396   7518275  31858204500   4237.4      1.3                          distance_b_a_c = neighbour_distance_b_a + distance_a_c
397   7518275  33470022717   4451.8      1.4                          if(distance_b_a_c < distance_b_c): # quicker to go via a
398    225357    956440656   4244.1      0.0                              node_b_update = True
399                                                           else: # b can't already get to c
400    796236   3415455549   4289.5      0.1                      distance_b_a_c = neighbour_distance_b_a + distance_a_c
401    796236   3412145520   4285.3      0.1                      if(distance_b_a_c < cutoff_distance): # not too for to go
402    593352   2514800052   4238.3      0.1                          node_b_update = True
403                                                                   
404                                                           ## Affinity distances update
405  41564609 164585250189   3959.7      6.8                  if node_b_update:
406    818709   3933555120   4804.6      0.2                      node_b_distances[node_c] = (distance_b_a_c, node_a)
407    818709   4151464335   5070.7      0.2                      if((node_b.type == Atom.BINDING_SITE) and (node_c.type == Atom.BINDING_SITE)):
408    104293   1704446289  16342.9      0.1                          node_b_chemical.add_affinityDistance(node_b, node_c, self.chemistry.affinity(node_b.data, node_c.data))
409    818709   3557529531   4345.3      0.1                      node_b_changed = True
410                                                       
411                                                       # If any of node b's rows have exceeded the cutoff distance, then remove them
412  42350234 197075504439   4653.5      8.1              for node_c, (distance_b_c, node_after_b) in node_b_distances.items(): # Can't use iteritems() here, as deleting from the dictionary
413  41838114 180297579789   4309.4      7.4                  if(distance_b_c > cutoff_distance):
414    206296    894881754   4337.9      0.0                      del node_b_distances[node_c]
415    206296    860508045   4171.2      0.0                      node_b_changed = True
416                                                               
417                                                               ## Affinity distances update
418    206296   4698692217  22776.5      0.2                      node_b_chemical.del_affinityDistance(node_b, node_c)
419                                                       
420                                                       # If we've modified node_b's distance table, tell its chemical to update accordingly
421    512120   2130466347   4160.1      0.1              if(node_b_changed):
422    217858   1201064454   5513.1      0.0                  node_b_chemical.nodes_changed.add(node_b)
423                                                   
424                                                   # Remove any neighbours that have infinite distance (have just unbound)
425                                                   ## TODO: not sure what difference it makes to do this here rather than above (after updating self.node_distances for neighbours)
426                                                   ##       but doing it above seems to break the walker's movement
427    737753   3830386968   5192.0      0.2          for (node_b, neighbour_distance_b_a) in self.neighbours[node_a].items(): # Can't use iteritems() here, as deleting from the dictionary
428    512120   2249770068   4393.1      0.1              if(neighbour_distance_b_a > cutoff_distance):
429       150       747747   4985.0      0.0                  del self.neighbours[node_a][node_b]
430                                                           
431                                                           ## Affinity distances update
432       150      2148813  14325.4      0.0                  self.del_affinityDistance(node_a, node_b)
    • 代码说明:**

此函数用于维护表示网络距离的稀疏距离矩阵(最短路径上的边权重之和)中的节点之间的(非常大的)网络。要处理完整的表并使用Floyd-Warshall algorithm将非常慢。(我先试了一下,而且它比当前版本慢了几个数量级。)因此,我的代码使用稀疏矩阵来表示全距离矩阵的阈值版本(忽略任何具有大于200个单位的距离的路径)。网络拓扑随时间改变,因此这个距离矩阵需要随着时间的推移而更新。为此,我使用了一个distance-vector routing protocol的粗略实现:网络中的每个节点都知道到路径上的其他节点和下一个节点的距离。当拓扑结构发生变化时,与该变化相关联的节点相应地更新它们的距离表,并通知它们的直接邻居。通过节点将它们的距离表发送到它们的邻居,节点更新它们的距离表并将它们传播到它们的邻居,信息在网络中传播。
有一个表示距离矩阵的对象:self.node_distances。这是一个将节点Map到路由表的字典。节点是我定义的一个对象。路由表是一个将节点Map到(distance,next_node). Distance是从node_a到node_b的图距离,并且next_node是你必须首先到达的node_a的邻居,在node_a和node_b之间的路径上。next_node为None表示node_a和node_b是图形邻居。例如,距离矩阵的示例可以是:

self.node_distances = { node_1 : { node_2 : (2.0, None),
                                   node_3 : (5.7, node_2),
                                   node_5 : (22.9, node_2) },
                        node_2 : { node_1 : (2.0, None),
                                   node_3 : (3.7, None),
                                   node_5 : (20.9, node_7)},
                        ...etc...

由于拓扑更改,两个相距很远(或根本不连接)的节点可能会变得很近。当发生这种情况时,会向此矩阵添加条目。由于阈值设置,两个节点可能会变得太远而无法处理。当发生这种情况时,会从此矩阵中删除条目。
self.neighbours矩阵类似于self.node_distances,但包含网络中直接链接(边)的信息。self.neighbours通过化学React不断地从外部修改此函数,这就是网络拓扑变化的原因。
我遇到问题的实际函数是:propagate_distances_node()执行distance-vector routing protocol的一个步骤。给定节点node_a,该函数确保node_a的邻居在距离矩阵中是正确的然后,该函数将node_a的路由表发送到网络中的所有node_a的直接邻居。它将node_a的路由表与每个邻居的路由表集成。自己的路由表。
在我的程序的其余部分,propagate_distances_node()函数被反复调用,直到距离矩阵收敛为止,并且维护一个集合self.nodes_changed,该集合由自上次更新以来改变了路由表的节点组成。选择这些节点的随机子集,并在其上调用propagate_distances_node(),这意味着节点异步且随机地展开其路由表。当集合self.nodes_changed变为空时,该算法收敛于真实距离矩阵。
"亲和距离"部分(add_affinityDistancedel_affinityDistance)是距离矩阵的(小)子矩阵的缓存,由程序的不同部分使用。

我这么做的原因是我在模拟参与React的化学物质的计算模拟,这是我博士学位的一部分。"化学物质"是"原子"的图形。(图中的节点)。两种化学品结合在一起被模拟为它们的两个图被新边连接。发生化学React(通过与此无关的复杂过程),改变了图形的拓扑结构。但是React中发生了什么取决于组成化学物质的不同原子之间的距离。所以对于模拟中的每一个原子,我想知道它和其他哪些原子靠得很近。一个稀疏的、有阈值的距离矩阵是存储这些信息的最有效的方式。由于网络的拓扑结构随着React的发生而改变,我需要更新矩阵。distance-vector routing protocol是我能想到的最快的方法。我不需要更复杂的路由协议,因为在我的特定应用程序中不会发生路由环路之类的情况(因为我的化学物质是如何构造的)。我随机地做的原因是这样我就可以把化学React过程和传播的距离交织在一起,并模拟随着React发生而随时间逐渐改变形状的化学物质(而不是立即改变形状)。
此函数中的self是表示化学品的对象。self.node_distances.keys()中的节点是组成化学品的原子。self.node_distances[node_x].keys()中的节点是来自化学品的节点,并且可能是来自与化学品结合(并与之React)的任何化学品的节点。

    • 更新日期:**

我尝试将node_x == node_y的每个示例替换为node_x is node_y(根据@Sven Marnach的评论),但这让事情变慢了!(我没想到会这样!)我原来的配置文件需要807.234s才能运行,但经过修改后,它增加到了895.895s。抱歉,我的配置文件出错了!我使用的是line_by_line,其中(在我的代码上)差异太大(大约90秒的差异都是噪音造成的)。当正确地分析它时,is绝对比==快。使用CProfile,我使用==的代码花费了34. 394秒,但使用is,它花了33. 535秒(我可以确认这是出了噪音)。

    • 更新:**现有库

我不确定是否会有一个现有的库可以做我想做的事情,因为我的要求是不寻常的:我需要计算加权无向图中所有节点对之间的最短路径长度,我只关心小于阈值的路径长度,计算完路径长度后,我对网络拓扑做了一个小的更改(添加或删除一条边),然后我想重新计算路径长度。与阈值相比,我的图很大(从给定的节点开始,图的大部分都比阈值更远),因此拓扑结构的变化不会影响大多数最短路径的长度,这就是我使用路由算法的原因:因为这会将拓扑变化信息传播到整个图结构中,所以当它超过阈值时,我可以停止传播。即,我不需要每次都重新计算所有路径。我可以使用以前的路径信息(从拓扑改变之前)来加速计算。这就是为什么我认为我的算法将比任何库实现的最短路径算法更快。我从来没有见过路由算法在物理网络中实际路由数据包之外使用(但如果有人见过,我会很感兴趣)。
NetworkX是@Thomas K提出的,它有lots of algorithms用于计算最短路径,它有一个算法用于计算带截断的all-pairs shortest path lengths(这是我想要的),但它只对未加权的图起作用(我的是加权的)。不幸的是,它的algorithms for weighted graphs不允许使用中断(这可能会使我的图表运行速度变慢),而且它的算法似乎都不支持在非常相似的网络上使用预先计算的路径(即路由材料)。
igraph是我知道的另一个图形库,但是在its documentation中,我找不到任何关于最短路径的信息,但是我可能错过了它--它的文档看起来不是很全面。
NumPy可能是可能的,感谢@9000的注解。如果我为节点的每个示例分配一个唯一的整数,我可以将稀疏矩阵存储在NumPy数组中。然后我可以用整数而不是节点示例来索引NumPy数组。我还需要两个NumPy数组:一个用于距离,另一个用于"next_node"引用,这可能比使用Python字典(我还不知道)要快。
有人知道其他可能有用的库吗?

    • 更新:**内存使用情况

我运行的是Windows(XP),所以这里有一些关于内存使用的信息,来自Process Explorer。CPU使用率为50%,因为我有一个双核机器。

我的程序没有耗尽RAM并开始命中交换。您可以从数字和没有任何活动的IO图中看到这一点。IO图上的尖峰是程序打印到屏幕上以说明其运行情况的位置。
然而,随着时间的推移,我的程序确实不断地使用越来越多的RAM,这可能不是一件好事(但总体上它并没有使用太多的RAM,这就是为什么我直到现在才注意到增加的原因)。
IO图上峰值之间的距离会随着时间的推移而增加。这很糟糕-我的程序每100,000次迭代就会打印到屏幕上,因此这意味着随着时间的推移,每次迭代的执行时间会更长...我已经通过长时间运行我的程序并测量打印语句之间的时间来证实这一点(程序每10,000次迭代之间的时间)。这应该是一个常数,但正如你从图中看到的,它是线性增加的......所以有一些东西在上面。(图中的噪音是因为我的程序使用了很多随机数,所以每次迭代的时间都是不同的。)

在我的程序运行了很长一段时间之后,内存使用情况如下所示(所以它肯定没有耗尽RAM):

yhived7q

yhived7q1#

node_after_b == node_a将尝试调用node_after_b.__eq__(node_a)

>>> class B(object):
...     def __eq__(self, other):
...         print "B.__eq__()"
...         return False
... 
>>> class A(object):
...     def __eq__(self, other):
...         print "A.__eq__()"
...         return False
... 
>>> a = A()
>>> b = B()
>>> a == b
A.__eq__()
False
>>> b == a
B.__eq__()
False
>>>

在使用C之前,尝试用优化版本覆盖Node.__eq__()
更新
我做了一个小实验(python 2.6.6):

#!/usr/bin/env python
# test.py
class A(object):
    def __init__(self, id):
        self.id = id

class B(A):
    def __eq__(self, other):
        return self.id == other.id

@profile
def main():
    list_a = []
    list_b = []
    for x in range(100000):
        list_a.append(A(x))
        list_b.append(B(x))

    ob_a = A(1)
    ob_b = B(1)
    for ob in list_a:
        if ob == ob_a:
            x = True
        if ob is ob_a:
            x = True
        if ob.id == ob_a.id:
            x = True
        if ob.id == 1:
            x = True
    for ob in list_b:
        if ob == ob_b:
            x = True
        if ob is ob_b:
            x = True
        if ob.id == ob_b.id:
            x = True
        if ob.id == 1:
            x = True

if __name__ == '__main__':
    main()

结果:

Timer unit: 1e-06 s

File: test.py Function: main at line 10 Total time: 5.52964 s

Line #      Hits         Time  Per Hit % Time  Line Contents
==============================================================
    10                                           @profile
    11                                           def main():
    12         1            5      5.0      0.0      list_a = []
    13         1            3      3.0      0.0      list_b = []
    14    100001       360677      3.6      6.5      for x in range(100000):
    15    100000       763593      7.6     13.8          list_a.append(A(x))
    16    100000       924822      9.2     16.7          list_b.append(B(x))
    17
    18         1           14     14.0      0.0      ob_a = A(1)
    19         1            5      5.0      0.0      ob_b = B(1)
    20    100001       500454      5.0      9.1      for ob in list_a:
    21    100000       267252      2.7      4.8          if ob == ob_a:
    22                                                       x = True
    23    100000       259075      2.6      4.7          if ob is ob_a:
    24                                                       x = True
    25    100000       539683      5.4      9.8          if ob.id == ob_a.id:
    26         1            3      3.0      0.0              x = True
    27    100000       271519      2.7      4.9          if ob.id == 1:
    28         1            3      3.0      0.0              x = True
    29    100001       296736      3.0      5.4      for ob in list_b:
    30    100000       472204      4.7      8.5          if ob == ob_b:
    31         1            4      4.0      0.0              x = True
    32    100000       283165      2.8      5.1          if ob is ob_b:
    33                                                       x = True
    34    100000       298839      3.0      5.4          if ob.id == ob_b.id:
    35         1            3      3.0      0.0              x = True
    36    100000       291576      2.9      5.3          if ob.id == 1:
    37         1            3      3.0      0.0              x = True

我非常惊讶:

  • "点"访问(ob.property)似乎非常昂贵(第25行与第27行)。
  • is和'=='之间没有太大区别,至少对于简单对象是这样

然后我尝试了更复杂的物体,结果与第一个实验一致。
如果您的数据集太大以至于无法容纳可用的RAM,我猜您可能会遇到与虚拟内存获取相关的某种I/O争用。
你运行的是Linux吗?如果是的话,你能在运行你的程序的时候发布一个你的机器的vmstat吗?给我们发送类似这样的输出:

vmstat 10 100

祝你好运!
更新(来自OP的评论)
我建议使用sys.setcheckinterval并启用/禁用GC,理由是对于这种特殊情况(大量示例),默认的GC引用计数检查有点昂贵,而且它的默认间隔太频繁了。
是的,我以前玩过sys.setcheckinterval,我把它改成了1000(从默认的100),但它没有做任何可测量的差异。禁用垃圾收集有所帮助-谢谢。这是迄今为止最大的加速-节省约20%(全程171分钟,下降到135分钟)-我不知道误差线是多少,但它肯定是一个统计上显著的增长。-Adam Nellis 2月9日15:10
我猜:
我认为Python GC是基于引用计数的,它会不时地检查每个示例的引用计数;由于您要遍历这些巨大的内存结构,因此在您的特定情况下,GC默认频率(1000个周期?)太频繁了--这是一种巨大的浪费。

ua4mk5z4

ua4mk5z42#

您是否考虑过Pyrex/Cython
它会自动将python编译成C,然后再编译成.pyd,所以它可能会在不做太多工作的情况下加快速度。

cunj1qz1

cunj1qz13#

这将需要相当数量的工作,但是...你可以考虑使用在GPU上运行的Floyd-Warshall。在使Floyd-Warshall在GPU上非常高效地运行方面已经做了很多工作。快速谷歌搜索会得到:
http://cvit.iiit.ac.in/papers/Pawan07accelerating.pdf
http://my.safaribooksonline.com/book/programming/graphics/9780321545411/gpu-computing-for-protein-structure-prediction/ch43lev1sec2#X2ludGVybmFsX0ZsYXNoUmVhZGVyP3htbGlkPTk3ODAzMjE1NDU0MTEvNDg3
http://www.gpucomputing.net/?q=node/1203
http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter43.html
尽管在Python中实现的Floyd-Warshall要慢一个数量级,但是在强大的GPU上运行的好的GPU版本仍然可能显著优于新的Python代码。
这里有一个轶事。我有一段简短、简单、计算密集型的代码,它做了一些类似于霍夫累加的事情。在Python中,我尽可能地优化了它,在高速的i7上花了大约7秒。然后我写了一个完全没有优化的GPU版本;在Nvidia GTX 480.YMMV上花费了大约0.002s,但是对于任何显著并行的东西,GPU很可能是一个长期的赢家,而且由于它是一个经过充分研究的算法,你应该能够利用现有的高度调优的代码。
对于Python / GPU桥,我建议使用PyCUDA或PyOpenCL。

ki0zmccv

ki0zmccv4#

我看不出你的代码在性能方面有什么问题(没有尝试去研究算法),你只是被大量的迭代所打击。你的代码的一部分被执行了4000万次!
请注意80%的时间是如何花在20%的代码上的--而这13行代码被执行了2400多万次。顺便说一句,通过这段代码,您为Pareto principle(或“20%的啤酒饮用者饮用了80%的啤酒”)提供了很好的说明。

要事优先:你试过Psycho吗?这是一个JIT编译器,可以大大提高代码的速度--考虑到大量的迭代--比如说4 - 5倍--你所要做的(当然是在下载和安装之后)就是在开头插入这个代码片段:

import psyco
psyco.full()

这就是为什么我喜欢Psycho,并在GCJ中也使用了它,在那里时间是至关重要的-没有代码,没有出错,并从添加的2行中突然增强。
回到吹毛求疵的问题上(因为时间上的%改进很小,所以会发生变化,比如用is替换==等等)。

Line    #   Hits    Time    Per Hit % Time  Line Contents
412 42350234    197075504439    4653.5  8.1 for node_c, (distance_b_c, node_after_b) in node_b_distances.items(): # Can't use iteritems() here, as deleting from the dictionary
386 42076729    184216680432    4378.1  7.6 for node_c, (distance_a_c, node_after_a) in self.node_distances[node_a].iteritems():
362 41756882    183992040153    4406.3  7.6 for node_c, (distance_b_c, node_after_b) in node_b_distances.iteritems(): # Think it's ok to modify items while iterating over them (just not insert/delete) (seems to work ok)
413 41838114    180297579789    4309.4  7.4 if(distance_b_c > cutoff_distance):
363 41244762    172425596985    4180.5  7.1 if(node_after_b == node_a):
389 41564609    172040284089    4139.1  7.1 if(node_c == node_b): # a-b path
388 41564609    171150289218    4117.7  7.1 node_b_update = False
391 41052489    169406668962    4126.6  7   elif(node_after_a == node_b): # a-b-a-b path
405 41564609    164585250189    3959.7  6.8 if node_b_update:
394 24004846    103404357180    4307.6  4.3 (distance_b_c, node_after_b) = node_b_distances[node_c]
395 24004846    102717271836    4279    4.2 if(node_after_b != node_a): # b doesn't already go to a first
393 24801082    101577038778    4095.7  4.2 elif(node_c in node_b_distances): # b can already get to c

A)除了你提到的代码行之外,我注意到#388在执行node_b_update = False时花费了相当多的时间。哦,等等--每次执行它时,都会在全局范围内查找False!为了避免这种情况,在该方法的开头赋值F, T = False, True,并将后面使用的FalseTrue替换为局部变量FT。这应该减少总时间,尽管只有一点点(3%?)。
B)我注意到#389中的情况“仅”出现了512,120次(基于#390的执行次数)与#391中的条件(16,251,407)。由于没有相关性,颠倒这些检查的顺序是有意义的--因为早期的“削减”应该不会给予什么推动作用我不确定完全避免pass语句是否会有帮助,但如果不损害可读性:

if (node_after_a is not node_b) and (node_c is not node_b):
   # neither a-b-a-b nor a-b path
   if (node_c in node_b_distances): # b can already get to c
       (distance_b_c, node_after_b) = node_b_distances[node_c]
       if (node_after_b is not node_a): # b doesn't already go to a first
           distance_b_a_c = neighbour_distance_b_a + distance_a_c
           if (distance_b_a_c < distance_b_c): # quicker to go via a
               node_b_update = T
   else: # b can't already get to c
       distance_b_a_c = neighbour_distance_b_a + distance_a_c
       if (distance_b_a_c < cutoff_distance): # not too for to go
           node_b_update = T

C)我刚刚注意到你在一个情况下使用try-except(#365-367)你只需要字典中的默认值-尝试使用.get(key, defaultVal)或者用collections.defaultdict(itertools.repeat(float('+inf')))创建你的字典。使用try-除了有它的代价-见#365报告3.5%的时间,这是设置堆栈帧和诸如此类的东西。
D)避免索引访问(可能的话,可以使用obj .字段或obj [ idx ])。例如,我看到您在多个地方使用self.node_distances[node_a](编号336、339、346、366、386),这意味着对于每次使用索引被使用两次(一次用于.,一次用于[])--当执行数千万次时,代价会很高。在我看来,你可以只在node_a_distances = self.node_distances[node_a]开始的方法上做,然后进一步使用它。

w1jd8yoj

w1jd8yoj5#

我本想把这个作为我问题的更新发布,但是堆栈溢出只允许在问题中包含30000个字符,所以我把这个作为答案发布。

**更新:**我迄今为止的最佳优化

我采纳了人们的建议,现在我的代码运行速度比以前快了21%,这很好--谢谢大家!
这是迄今为止我做得最好的一次,我将所有==测试替换为针对节点的is测试,禁用垃圾收集,并按照@ NasBanov的建议重写了第388行的大if语句部分,还添加了众所周知的try/except技巧来避免测试(第390行-删除测试node_c in node_b_distances),这有助于加载,因为它几乎不抛出异常。我试着切换第391行和第392行,并将node_b_distances[node_c]赋给一个变量,但这种方法是最快的。
但是,我仍然没有找到内存泄漏(见我的问题中的图表)。但是我认为这可能是在我的代码的不同部分(我还没有在这里发布)。如果我能修复内存泄漏,那么这个程序将运行得足够快,我可以用途:)

Timer unit: 3.33366e-10 s
File: routing_distances.py
Function: propagate_distances_node at line 328
Total time: 760.74 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
328                                               @profile
329                                               def propagate_distances_node(self, node_a, cutoff_distance=200):
330                                                       
331                                                   # a makes sure its immediate neighbours are correctly in its distance table
332                                                   # because its immediate neighbours may change as binds/folding change
333    791349   4158169713   5254.5      0.2          for (node_b, neighbour_distance_b_a) in self.neighbours[node_a].iteritems():
334    550522   2331886050   4235.8      0.1              use_neighbour_link = False
335                                                       
336    550522   2935995237   5333.1      0.1              if(node_b not in self.node_distances[node_a]): # a doesn't know distance to b
337     15931     68829156   4320.5      0.0                  use_neighbour_link = True
338                                                       else: # a does know distance to b
339    534591   2728134153   5103.2      0.1                  (node_distance_b_a, next_node) = self.node_distances[node_a][node_b]
340    534591   2376374859   4445.2      0.1                  if(node_distance_b_a > neighbour_distance_b_a): # neighbour distance is shorter
341        78       347355   4453.3      0.0                      use_neighbour_link = True
342    534513   3145889079   5885.5      0.1                  elif((None is next_node) and (float('+inf') == neighbour_distance_b_a)): # direct route that has just broken
343        74       327600   4427.0      0.0                      use_neighbour_link = True
344                                                               
345    550522   2414669022   4386.1      0.1              if(use_neighbour_link):
346     16083     81850626   5089.3      0.0                  self.node_distances[node_a][node_b] = (neighbour_distance_b_a, None)
347     16083     87064200   5413.4      0.0                  self.nodes_changed.add(node_a)
348                                                           
349                                                           ## Affinity distances update
350     16083     86580603   5383.4      0.0                  if((node_a.type == Atom.BINDING_SITE) and (node_b.type == Atom.BINDING_SITE)):
351       234      6656868  28448.2      0.0                      self.add_affinityDistance(node_a, node_b, self.chemistry.affinity(node_a.data, node_b.data))     
352                                                   
353                                                   # a sends its table to all its immediate neighbours
354    791349   4034651958   5098.4      0.2          for (node_b, neighbour_distance_b_a) in self.neighbours[node_a].iteritems():
355    550522   2392248546   4345.4      0.1              node_b_changed = False
356                                               
357                                                       # b integrates a's distance table with its own
358    550522   2520330696   4578.1      0.1              node_b_chemical = node_b.chemical
359    550522   2734341975   4966.8      0.1              node_b_distances = node_b_chemical.node_distances[node_b]
360                                                       
361                                                       # For all b's routes (to c) that go to a first, update their distances
362  46679347 222161837193   4759.3      9.7              for node_c, (distance_b_c, node_after_b) in node_b_distances.iteritems(): # Think it's ok to modify items while iterating over them (just not insert/delete) (seems to work ok)
363  46128825 211963639122   4595.0      9.3                  if(node_after_b is node_a):
364                                                               
365  18677439  79225517916   4241.8      3.5                      try:
366  18677439 101527287264   5435.8      4.4                          distance_b_a_c = neighbour_distance_b_a + self.node_distances[node_a][node_c][0]
367    181510    985441680   5429.1      0.0                      except KeyError:
368    181510   1166118921   6424.5      0.1                          distance_b_a_c = float('+inf')
369                                                                   
370  18677439  89626381965   4798.6      3.9                      if(distance_b_c != distance_b_a_c): # a's distance to c has changed
371    692131   3352970709   4844.4      0.1                          node_b_distances[node_c] = (distance_b_a_c, node_a)
372    692131   3066946866   4431.2      0.1                          node_b_changed = True
373                                                                   
374                                                                   ## Affinity distances update
375    692131   3808548270   5502.6      0.2                          if((node_b.type == Atom.BINDING_SITE) and (node_c.type == Atom.BINDING_SITE)):
376     96794   1655818011  17106.6      0.1                              node_b_chemical.add_affinityDistance(node_b, node_c, self.chemistry.affinity(node_b.data, node_c.data))
377                                                                   
378                                                               # If distance got longer, then ask b's neighbours to update
379                                                               ## TODO: document this!
380  18677439  88838493705   4756.5      3.9                      if(distance_b_a_c > distance_b_c):
381                                                                   #for (node, neighbour_distance) in node_b_chemical.neighbours[node_b].iteritems():
382   1656796   7949850642   4798.3      0.3                          for node in node_b_chemical.neighbours[node_b]:
383   1172486   6307264854   5379.4      0.3                              node.chemical.nodes_changed.add(node)
384                                                       
385                                                       # Look for routes from a to c that are quicker than ones b knows already
386  46999631 227198060532   4834.0     10.0              for node_c, (distance_a_c, node_after_a) in self.node_distances[node_a].iteritems():
387                                                           
388  46449109 218024862372   4693.8      9.6                  if((node_after_a is not node_b) and # not a-b-a-b path
389  28049321 126269403795   4501.7      5.5                     (node_c is not node_b)):         # not a-b path
390  27768341 121588366824   4378.7      5.3                      try: # Assume node_c in node_b_distances ('try' block will raise KeyError if not)
391  27768341 159413637753   5740.8      7.0                          if((node_b_distances[node_c][1] is not node_a) and # b doesn't already go to a first
392   8462467  51890478453   6131.8      2.3                             ((neighbour_distance_b_a + distance_a_c) < node_b_distances[node_c][0])):
393                                                               
394                                                                       # Found a route
395    224593   1168129548   5201.1      0.1                              node_b_distances[node_c] = (neighbour_distance_b_a + distance_a_c, node_a)
396                                                                       ## Affinity distances update
397    224593   1274631354   5675.3      0.1                              if((node_b.type == Atom.BINDING_SITE) and (node_c.type == Atom.BINDING_SITE)):
398     32108    551523249  17177.1      0.0                                  node_b_chemical.add_affinityDistance(node_b, node_c, self.chemistry.affinity(node_b.data, node_c.data))
399    224593   1165878108   5191.1      0.1                              node_b_changed = True
400                                                                       
401    809945   4449080808   5493.1      0.2                      except KeyError:
402                                                                   # b can't already get to c (node_c not in node_b_distances)
403    809945   4208032422   5195.5      0.2                          if((neighbour_distance_b_a + distance_a_c) < cutoff_distance): # not too for to go
404                                                                       
405                                                                       # These lines of code copied, for efficiency 
406                                                                       #  (most of the time, the 'try' block succeeds, so don't bother testing for (node_c in node_b_distances))
407                                                                       # Found a route
408    587726   3162939543   5381.7      0.1                              node_b_distances[node_c] = (neighbour_distance_b_a + distance_a_c, node_a)
409                                                                       ## Affinity distances update
410    587726   3363869061   5723.5      0.1                              if((node_b.type == Atom.BINDING_SITE) and (node_c.type == Atom.BINDING_SITE)):
411     71659   1258910784  17568.1      0.1                                  node_b_chemical.add_affinityDistance(node_b, node_c, self.chemistry.affinity(node_b.data, node_c.data))
412    587726   2706161481   4604.5      0.1                              node_b_changed = True
413                                                                   
414                                                               
415                                                       
416                                                       # If any of node b's rows have exceeded the cutoff distance, then remove them
417  47267073 239847142446   5074.3     10.5              for node_c, (distance_b_c, node_after_b) in node_b_distances.items(): # Can't use iteritems() here, as deleting from the dictionary
418  46716551 242694352980   5195.0     10.6                  if(distance_b_c > cutoff_distance):
419    200755    967443975   4819.0      0.0                      del node_b_distances[node_c]
420    200755    930470616   4634.9      0.0                      node_b_changed = True
421                                                               
422                                                               ## Affinity distances update
423    200755   4717125063  23496.9      0.2                      node_b_chemical.del_affinityDistance(node_b, node_c)
424                                                       
425                                                       # If we've modified node_b's distance table, tell its chemical to update accordingly
426    550522   2684634615   4876.5      0.1              if(node_b_changed):
427    235034   1383213780   5885.2      0.1                  node_b_chemical.nodes_changed.add(node_b)
428                                                   
429                                                   # Remove any neighbours that have infinite distance (have just unbound)
430                                                   ## TODO: not sure what difference it makes to do this here rather than above (after updating self.node_distances for neighbours)
431                                                   ##       but doing it above seems to break the walker's movement
432    791349   4367879451   5519.5      0.2          for (node_b, neighbour_distance_b_a) in self.neighbours[node_a].items(): # Can't use iteritems() here, as deleting from the dictionary
433    550522   2968919613   5392.9      0.1              if(neighbour_distance_b_a > cutoff_distance):
434       148       775638   5240.8      0.0                  del self.neighbours[node_a][node_b]
435                                                           
436                                                           ## Affinity distances update
437       148      2096343  14164.5      0.0                  self.del_affinityDistance(node_a, node_b)

相关问题