[英]Optimising Python dictionary access code
题:
我已经将我的Python程序分析为死亡,并且有一个函数可以减慢一切。 它大量使用Python字典,所以我可能没有以最好的方式使用它们。 如果我无法让它运行得更快,我将不得不用C ++重新编写它,那么是否有人可以帮我在Python中优化它?
我希望我已经给出了正确的解释,并且你可以对我的代码有所了解! 在此先感谢您的帮助。
我的代码:
这是违规函数,使用line_profiler和kernprof进行分析 。 我正在运行Python 2.7
我对第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算法会非常慢。 (我首先尝试了这个,它比当前版本慢了几个数量级。)所以我的代码使用稀疏矩阵来表示全距离矩阵的阈值版本(忽略距离大于200个单位的任何路径)。 网络拓扑随着时间的推移而变化,因此该距离矩阵需要随时间更新。 为此,我使用距离矢量路由协议的粗略实现:网络中的每个节点都知道到每个其他节点和路径上的下一个节点的距离。 当发生拓扑更改时,与此更改关联的节点会相应地更新其距离表,并告知其直接邻居。 信息通过网络将节点发送到他们的邻居,他们更新他们的距离表并将它们传播给他们的邻居。
有一个表示距离矩阵的对象: self.node_distances
。 这是一个将节点映射到路由表的字典。 节点是我定义的对象。 路由表是将节点映射到(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
通过化学反应不断地在外部修改。 这是网络拓扑变化的来源。
我遇到问题的实际功能: propagate_distances_node()
执行距离矢量路由协议的一个步骤。 给定节点node_a
,该函数确保node_a
的邻居在距离矩阵中正确(拓扑变化)。 然后,该函数将node_a
的路由表发送到网络中所有node_a
的直接邻居。 它将node_a
的路由表与每个邻居自己的路由表集成node_a
。
在我的程序的其余部分中,重复调用propagate_distances_node()
函数,直到距离矩阵收敛。 维护自上次更新以来已更改其路由表的节点的集合self.nodes_changed
。 在我算法的每次迭代中,选择这些节点的随机子集,并在它们上调用propagate_distances_node()
。 这意味着节点以异步和随机方式扩展其路由表。 当set self.nodes_changed
变空时,该算法收敛于真实距离矩阵。
“亲和距离”部分( add_affinityDistance
和del_affinityDistance
)是距离矩阵的(小)子矩阵的高速缓存,由程序的不同部分使用。
我这样做的原因是我正在模拟参与反应的化学物质的计算类似物,作为我博士学位的一部分。 “化学物质”是“原子”(图中的节点)的图表。 模拟两种结合在一起的化学物质,因为它们的两个图形由新边缘连接。 发生化学反应(通过这里不相关的复杂过程),改变图的拓扑结构。 但是反应中发生的事情取决于构成化学物质的不同原子之间的距离。 因此,对于模拟中的每个原子,我想知道它接近哪个其他原子。 稀疏的阈值距离矩阵是存储该信息的最有效方式。 由于网络拓扑随着反应的发生而变化,我需要更新矩阵。 距离矢量路由协议是我能做到这一点的最快方法。 我不需要更复杂的路由协议,因为路由循环之类的东西不会在我的特定应用程序中发生(因为我的化学品是如何构建的)。 我随机做的原因是我可以将化学反应过程与距离扩散交错,并随着时间的推移模拟化学物质逐渐变化的形状(而不是立即改变形状)。
该函数中的self
是表示化学物质的对象。 self.node_distances.keys()
中的节点是构成化学物质的原子。 self.node_distances[node_x].keys()
中的节点是来自化学品的节点和来自化学品结合(并与之反应)的任何化学品的潜在节点。
更新:
我尝试用node_x == node_y
替换node_x == node_y
每个实例node_x is node_y
(根据@Sven Marnach的评论), 但它减慢了速度! (我没想到!)我的原始配置文件需要807.234才能运行,但是通过这个修改它增加到895.895s。 对不起,我正在做错误的分析! 我使用的是line_by_line,它(在我的代码中)有太多的变化(大约90秒的差异都在噪音中)。 当正确剖析它, is
为detinitely快于==
。 使用CPROFILE ,我与代码==
了34.394s,但is
,花了33.535s(我可以确认是出了噪声)。
更新:现有库
我不确定是否会有一个可以做我想要的现有库,因为我的要求很不寻常:我需要计算加权无向图中所有节点对之间的最短路径长度。 我只关心低于阈值的路径长度。 在计算路径长度后,我对网络拓扑进行了一些小改动(添加或删除边缘),然后我想重新计算路径长度。 与阈值相比,我的图形很大(来自给定节点,大部分图形远离阈值),因此拓扑更改不会影响大多数最短路径长度。 这就是我使用路由算法的原因:因为这会通过图形结构传播拓扑变化信息,所以当它超过阈值时我可以停止传播它。 也就是说,我不需要每次都重新计算所有路径。 我可以使用先前的路径信息(从拓扑更改之前)来加速计算。 这就是为什么我认为我的算法比任何最短路径算法的库实现都要快。 我从未见过在实际通过物理网络路由数据包之外使用的路由算法(但如果有人有,那么我会感兴趣)。
NetworkX由@Thomas K提出。它有许多用于计算最短路径的算法 。 它有一个算法用于计算具有截止值的所有对最短路径长度 (这是我想要的),但它仅适用于未加权的图形(我的加权)。 不幸的是,它的加权图算法不允许使用截止(这可能会使我们的图表变慢)。 并且它的算法似乎都不支持在非常相似的网络上使用预先计算的路径(即路由选择)。
igraph是我所知道的另一个图库,但是看看它的文档 ,我找不到任何关于最短路径的东西。 但我可能错过了它 - 它的文档似乎并不全面。
由于@ 9000的评论, NumPy可能是可能的。 如果我为每个节点实例分配一个唯一的整数,我可以将我的稀疏矩阵存储在NumPy数组中。 然后,我可以使用整数而不是节点实例索引NumPy数组。 我还需要两个NumPy数组:一个用于距离,一个用于“next_node”引用。 这可能比使用Python词典更快(我还不知道)。
有谁知道任何其他可能有用的库?
更新:内存使用情况
我正在运行Windows(XP),所以这里有一些关于内存使用的信息,来自Process Explorer 。 CPU使用率为50%,因为我有一台双核机器。
我的程序没有耗尽RAM并开始点击交换。 您可以从数字和IO图表中看到没有任何活动。 IO图表上的峰值是程序打印到屏幕上的位置,以说明它是如何进行的。
但是,我的程序确实会随着时间的推移继续使用越来越多的RAM,这可能不是一件好事(但它并没有耗费大量的RAM,这就是为什么我直到现在才注意到增加的原因)。
并且IO图上的尖峰之间的距离随着时间的推移而增加。 这很糟糕 - 我的程序每隔100,000次迭代打印到屏幕上,这意味着随着时间的推移,每次迭代都需要更长的时间来执行......我已经通过长时间执行程序并测量它之间的时间来证实这一点。 print语句(程序每10,000次迭代之间的时间)。 这应该是不变的,但正如你从图中看到的那样,它会线性增加......所以有些东西在那里。 (此图表上的噪音是因为我的程序使用了大量随机数,因此每次迭代的时间会有所不同。)
我的程序运行了很长时间后,内存使用情况看起来像这样(所以它肯定没有用完RAM):
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
>>>
尝试使用优化版本覆盖Node.__eq__()
,然后再使用C.
UPDATE
我做了这个小实验(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
我很惊讶:
然后我尝试了更复杂的对象,结果与第一个实验一致。
你交换了很多吗? 如果您的数据集太大而不适合可用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个周期?)经常消失 - 这是一个巨大的浪费。 - 你真的2月10日凌晨2点06分
这需要相当多的工作,但是......你可能会考虑使用在GPU上运行的Floyd-Warshall。 在使用Floyd-Warshall在GPU上高效运行方面已经做了很多工作。 快速谷歌搜索产生:
http://cvit.iiit.ac.in/papers/Pawan07accelerating.pdf
http://www.gpucomputing.net/?q=node/1203
http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter43.html
即使在Python中实现,Floyd-Warshall的速度要慢一个数量级,但强大的GPU上的GPU版本可能仍会明显优于新的Python代码。
这是一个轶事。 我有一个简短的,计算密集的代码片段,它做了类似于hough积累的东西。 在Python中,我已经优化了它,它在快速的i7上花费了大约7秒。 然后我写了一个完全非优化的GPU版本; 在Nvidia GTX 480上花费了大约0.002秒.YMMV,但对于任何显着平行的东西,GPU可能是一个长期的赢家,因为它是一个经过充分研究的算法,你应该能够利用现有的高度调整的代码。
对于Python / GPU桥,我建议使用PyCUDA或PyOpenCL。
我没有看到你的代码有关性能的任何问题(没有尝试算法),你只是被大量的迭代所击中。 部分代码执行4000 万次!
请注意80%的时间是如何花费在20%的代码中的 - 而那些是执行了2400多万次的13行。 顺便说一句,您可以通过此代码为帕累托原则 (或“20%的啤酒饮用者饮用80%的啤酒”)提供很好的说明。
首先要做的事情 :你有没有尝试过Psycho ? 它是一个JIT编译器,可以大大加快你的代码 - 考虑到大量的迭代 - 比如说4倍-5倍 - 你需要做的就是(当然下载和安装之后)将这个片段插入到开始:
import psyco
psyco.full()
这就是为什么我喜欢Psycho并且也在GCJ中使用它,时间是至关重要的 - 没有任何代码,没有任何错误,并且从2行添加突然增强。
回到吹毛求疵(更改像更换==
与is
等是,由于小%的时间改善)。 这里他们是13条“有过错”:
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
,并将False
和True
后期使用替换为本地F
和T
这应该减少总时间,尽管很少(3%?)。
B)我注意到#389中的条件“仅”发生512,120次(基于#390的执行次数)与#391中的条件16,251,407。 由于没有依赖性,因此反转这些检查的顺序是有意义的 - 因为早期的“削减”应该提供很少的提升(2%?)。 我不确定是否完全避免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')))
创建词典collections.defaultdict(itertools.repeat(float('+inf')))
。 使用try-except有它的价格 - 请参阅#365报告3.5%的时间,即设置堆栈帧等等。
d)避免索引访问(与物镜是它.
字段或OBJ [
IDX ]
)可能时。 例如,我看到你在多个地方使用self.node_distances[node_a]
(# self.node_distances[node_a]
),这意味着每次使用索引都会被使用两次(一次用于.
和一次用于[]
) - 并且执行数千万次后变得昂贵。 在我看来,您可以在开始node_a_distances = self.node_distances[node_a]
,然后再使用它。
我会发布这个作为我的问题的更新,但Stack Overflow只允许30000个字符的问题,所以我发布这个作为答案。
更新:到目前为止我的最佳优化
我接受了人们的建议,现在我的代码比以前快了大约21%,这很好 - 谢谢大家!
这是迄今为止我设法做到的最好的。 我已经全部换成了==
测试用is
为节点,禁用垃圾收集和重新编写大if
声明的部分在388线,与@Nas Banov的建议一致。 我添加了众所周知的try/except
技巧以避免测试(第390行 - 删除node_c in node_b_distances
的测试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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.