如何优化Python字典的使用?
问题:
我对我的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
。这是一个字典,将节点映射到路由表。节点是我定义的一个对象。路由表是一个字典,将节点映射到(距离,下一个节点)的元组。距离是从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
的路由表与每个邻居自己的路由表整合在一起。
在我程序的其余部分中,propagate_distances_node()
函数会被反复调用,直到距离矩阵收敛。维护一个集合self.nodes_changed
,记录自上次更新以来已更改路由表的节点。在算法的每次迭代中,随机选择这些节点的一个子集,并对它们调用propagate_distances_node()
。这意味着节点以异步和随机的方式传播它们的路由表。当集合self.nodes_changed
变为空时,这个算法就收敛到真实的距离矩阵。
关于“亲和距离”的部分(add_affinityDistance
和del_affinityDistance
)是距离矩阵的一个(小)子矩阵的缓存,用于程序的其他部分。
我这样做的原因是,我正在模拟参与反应的化学物质的计算类比,作为我博士研究的一部分。“化学物质”是一个“原子”(图中的节点)图。两个化学物质结合在一起被模拟为它们的两个图通过新边连接。化学反应发生(通过一个复杂的过程,这里不相关),改变图的拓扑。但反应中发生的事情取决于构成化学物质的不同原子之间的距离。因此,对于模拟中的每个原子,我想知道它与哪些其他原子是接近的。稀疏的、阈值的距离矩阵是存储这些信息的最有效方式。由于网络的拓扑在反应发生时会变化,我需要更新矩阵。距离向量路由协议是我能想到的最快的实现方式。我不需要更复杂的路由协议,因为在我特定的应用中,像路由循环这样的情况不会发生(因为我的化学物质的结构)。我之所以采用随机方式,是为了能够将化学反应过程与距离传播交错进行,模拟化学物质在反应发生时逐渐改变形状(而不是瞬间改变形状)。
函数中的self
是一个表示化学物质的对象。self.node_distances.keys()
中的节点是构成化学物质的原子。self.node_distances[node_x].keys()
中的节点是化学物质的节点,以及可能与其结合(并反应)的任何化学物质的节点。
更新:
我尝试将每个node_x == node_y
的实例替换为node_x is node_y
(根据@Sven Marnach的评论),但这反而让速度变慢了!(我没想到会这样!)我原来的性能分析运行了807.234秒,但这个修改后增加到895.895秒。抱歉,我之前的性能分析做错了!我使用了逐行分析,这在我的代码中变异太大(大约90秒的差异都是噪声)。在正确分析后,is
确实比==
快。使用CProfile时,我的代码用==
运行了34.394秒,而用is
时只用了33.535秒(我可以确认这是超出噪声的)。
更新:现有库
我不确定是否会有现成的库可以满足我的需求,因为我的要求比较特殊:我需要计算加权无向图中所有节点对之间的最短路径长度。我只关心低于阈值的路径长度。在计算路径长度后,我会对网络拓扑进行小的修改(添加或删除一条边),然后我想重新计算路径长度。我的图与阈值相比非常大(从给定节点开始,大部分图都在阈值之外),因此拓扑变化不会影响大多数最短路径长度。这就是我使用路由算法的原因:因为这可以将拓扑变化的信息传播到图结构中,这样我可以在信息传播超过阈值时停止。也就是说,我不需要每次都重新计算所有路径。我可以利用拓扑变化之前的路径信息来加快计算。这就是我认为我的算法会比任何库实现的最短路径算法快的原因。
我从未见过路由算法用于实际的物理网络之外的地方(但如果有人见过,我会很感兴趣)。
NetworkX是@Thomas K.推荐的。它有很多计算最短路径的算法。它有一个计算所有节点对最短路径长度的算法,并且可以设置截止值(这正是我想要的),但它只适用于无权图(而我的图是加权的)。不幸的是,它的加权图算法不允许使用截止值(这可能会让它们在我的图上变得很慢)。而且它的算法似乎都不支持在非常相似的网络上使用预计算的路径(也就是路由的东西)。
igraph是我知道的另一个图形库,但查看它的文档,我找不到任何关于最短路径的信息。但我可能错过了,因为它的文档似乎不太全面。
NumPy可能是可行的,感谢@9000的评论。如果我给每个节点实例分配一个唯一的整数,我可以将稀疏矩阵存储在NumPy数组中。然后我可以用整数来索引NumPy数组,而不是用节点实例。我还需要两个NumPy数组:一个用于距离,另一个用于“下一个节点”的引用。这可能比使用Python字典更快(我还不确定)。
有没有人知道其他可能有用的库?
更新:内存使用情况
我在运行Windows(XP),这里是一些关于内存使用情况的信息,来自Process Explorer。CPU使用率为50%,因为我有一台双核机器。
我的程序没有耗尽内存而开始使用交换空间。从数字和IO图表没有活动可以看出这一点。IO图表上的尖峰是程序打印到屏幕上以显示其进度的地方。
然而,我的程序确实随着时间的推移不断使用更多的RAM,这可能不是好事(但总体上并没有使用太多的RAM,这就是我直到现在才注意到增加的原因)。
而且,IO图表上尖峰之间的距离随着时间的推移在增加。这是个坏消息——我的程序每100,000次迭代打印一次,所以这意味着每次迭代的执行时间随着时间的推移而变长……我通过长时间运行程序并测量打印语句之间的时间(程序每10,000次迭代之间的时间)确认了这一点。这应该是恒定的,但正如你从图中看到的,它线性增加……所以肯定有什么问题。(这个图的噪声是因为我的程序使用了很多随机数,所以每次迭代的时间会有所不同。)
在我的程序运行很长时间后,内存使用情况如下(所以肯定没有耗尽内存):
5 个回答
我觉得你的代码在性能方面没有什么问题(不考虑算法本身),你只是遇到了大量的循环次数。你代码中的某些部分执行了四千万次!
注意,80%的时间花在了20%的代码上,而这20%的代码就是那13行执行了超过2400万次。顺便提一下,你的代码很好地展示了帕累托原则(也就是“20%的喝酒者喝了80%的酒”)。
首先:你有没有试过Psycho? 这是一个即时编译器,可以大大加速你的代码——考虑到循环次数这么多,速度可能提升4到5倍——你只需要做的就是(当然,先下载和安装)在开头插入这段代码:
import psyco
psyco.full()
这就是我喜欢Psycho并在GCJ中使用它的原因,因为时间很重要——不需要额外编码,不会出错,只需添加两行就能获得显著提升。
回到细节问题(像把==
换成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
,然后在后面的代码中用局部变量F
和T
替代False
和True
。这样应该能稍微减少整体时间,虽然可能只有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) 我刚注意到你在第365-367行使用了try-except
,但你其实只需要从字典中获取默认值——可以试试用.get(key, defaultVal)
,或者用collections.defaultdict(itertools.repeat(float('+inf')))
来创建字典。使用try-except
是有代价的——第365行报告了3.5%的时间,这部分时间是用来设置栈帧等的。
D) 尽量避免索引访问(无论是用obj.
field还是obj[
idx]
)当可以的时候。例如,我看到你在多个地方使用self.node_distances[node_a]
(第336、339、346、366、386行),这意味着每次使用时索引都要执行两次(一次是.
,一次是[]
)——当执行数千万次时,这样会很耗时。看起来你可以在方法开始时这样做node_a_distances = self.node_distances[node_a]
,然后在后面的代码中使用这个变量。
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 和 '==' 之间的差别不大。
然后我尝试了更复杂的对象,结果和第一次实验一致。
你是不是在频繁交换数据?如果你的数据集太大,超出了可用的内存,我猜你可能会遇到与虚拟内存获取相关的输入输出争用问题。
你是在运行Linux吗?如果是的话,能否在运行你的程序时发一份你机器的vmstat?给我们发送类似下面的输出:
vmstat 10 100
祝你好运!
更新(来自提问者的评论)
我建议尝试调整 sys.setcheckinterval 并启用/禁用垃圾回收(GC)。这样做的原因是,对于这种特定情况(大量实例),默认的垃圾回收引用计数检查有点耗费资源,而它的默认间隔设置得太频繁了。
是的,我之前玩过 sys.setcheckinterval。我把它改成了 1000(从默认的 100),但没有明显的效果。禁用垃圾回收确实有帮助——谢谢。这是到目前为止最大的提速——节省了大约 20%(整个运行时间从171分钟降到135分钟)——我不确定这个结果的误差范围,但这肯定是一个统计上显著的提升。– Adam Nellis 2月9日 15:10
我的猜测:
我认为Python的垃圾回收是基于引用计数的。它会不时检查每个实例的引用计数;由于你在遍历这些庞大的内存结构,在你这种特定情况下,垃圾回收的默认频率(1000个周期?)实在是太频繁了——这是一种巨大的浪费。– Yours Truly 2月10日 2:06