我有一些代码很慢(最后计数30-60分钟),我需要优化,它是Abaqus的结构工程模型的数据提取脚本。脚本最糟糕的部分是循环,它首先逐帧迭代对象模型数据库(即模拟时间历史中的时间),并在其下嵌套,它由每个节点迭代。愚蠢的是,有~100k'节点',但只有约20k个有用节点。但幸运的是,对于我来说节点总是处于相同的顺序,这意味着我不需要查找节点的uniqueLabel,我可以在一个单独的循环中执行此操作,然后对最终得到的内容进行过滤。这就是我将所有内容转储到一个列表然后删除所有重复节点的原因。但正如你从代码中看到的那样:
timeValues = []
peeqValues = []
for frame in frames: #760 loops
setValues = frame.fieldOutputs['@@@fieldOutputType'].getSubset
region=abaqusSet, position=ELEMENT_NODAL).values
timeValues.append(frame.frameValue)
for value in setValues: # 100k loops
peeqValues.append(value.data)
它仍然需要不必要地进行value.data
次呼叫,大约80k次。如果有人熟悉Abaqus odb(对象数据库)对象,它们在python下会非常慢。为了增加对伤害的侮辱,他们只在一个线程中运行,在Abaqus下有自己的python版本(2.6.x)和包(所以例如numpy可用,pandas不是)。可能令人讨厌的另一件事是你可以按位置寻址物体,例如frames[-1]
为您提供最后一帧,但您无法切片,例如你不能这样做:for frame in frames[0:10]: # iterate first 10 elements
。
我对itertools没有任何经验,但我想提供一个nodeIDs列表(或True / False列表)以映射到setValues。要跳过的setValues的长度和模式对于760帧中的每一帧始终是相同的。也许是这样的:
for frame in frames: #still 760 calls
setValues = frame.fieldOutputs['@@@fieldOutputType'].getSubset(
region=abaqusSet, position=ELEMENT_NODAL).values
timeValues.append(frame.frameValue)
# nodeSet_IDs_TF = [True, True, False, False, False, ...] same length as
# setValues
filteredSetValues = ifilter(nodeSet_IDs_TF, setValues)
for value in filteredSetValues: # only 20k calls
peeqValues.append(value.data)
任何其他提示也很感激,在此之后我确实希望通过从循环中删除.append()
来“避免点”,然后将整个事物放入函数中以查看它是否有帮助。整个脚本已经在1.5小时内运行(从6点开始,一点点21点运行),但是一旦开始优化,就无法停止。
内存考虑因素也很受欢迎,我在群集上运行这些内容,我相信一次使用80 GB的RAM就可以了。这些脚本肯定适用于160 GB,问题是将资源分配给我。
我一直在寻找解决方案,但也许我使用了错误的关键字,我确信这在循环中不是一个不常见的问题。
编辑1
以下是我最终使用的内容:
# there is no compress under 2.6.x ... so use the equivalent recipe:
from itertools import izip
def compress(data, selectors):
# compress('ABCDEF', [1,0,1,0,1,1]) --> ACEF
return (d for d, s in izip(data, selectors) if s)
def iterateOdb(frames, selectors): # minor speed up
peeqValues = []
timeValues = []
append = peeqValues.append # minor speed up
for frame in frames:
setValues = frame.fieldOutputs['@@@fieldOutputType'].getSubset(
region=abaqusSet, position=ELEMENT_NODAL).values
timeValues.append(frame.frameValue)
for value in compress(setValues, selectors): # massive speed up
append(value.data)
return peeqValues, timeValues
peeqValues, timeValues = iterateOdb(frames, selectors)
最大的改进来自于使用compress(values, selectors)
方法(整个脚本,包括odb部分从大约1:30小时到25分钟。来自append = peeqValues.append
的小改进以及将所有内容括在def iterateOdb(frames, selectors):
。
我使用了来自https://wiki.python.org/moin/PythonSpeed/PerformanceTips
的提示感谢大家回答&帮助!
答案 0 :(得分:1)
如果您对itertools没有信心,请先尝试在for循环中使用if语句。
例如
for index, item in enumerate(values):
if not selectors[index]:
continue
...
# where selectors is a truth array like nodeSet_IDs_TF
通过这种方式,您可以更加确定自己获得了正确的行为,并且可以获得使用itertools获得的大部分性能提升。
等效的itertools是compress
。
for item in compress(values, selectors):
...
我对abaqus并不熟悉,但您可以实现的最佳优化方法是查看是否有任何方式可以为您的选择器提供abaqus,因此不必浪费创建每个值,仅适用于它被扔掉了。如果使用abaqus进行基于数组的大型数据操作,那么就是这种情况。
答案 1 :(得分:1)
除了Dunes
的解决方案之外的另一种变体:
for value, selector in zip(setValues, selectors):
if selector:
peeqValue.append(value.data)
如果要保持输出列表长度与setValue
长度相同,则添加else
子句:
for value, selector in zip(setValues, selectors):
if selector:
peeqValue.append(value.data)
else:
peeqValue.append(None)
selector
这是一个带有True
/ False
的向量,其长度与setValues
相同。
在这种情况下,你真正喜欢的是品味。如果7600万个节点(760 x 100 000)的完整迭代需要30分钟,那么时间就不会花费在python的循环中。
我试过了:
def loopit(a):
for i in range(760):
for j in range(100000):
a = a + 1
return a
IPython
的{{1}}报告循环时间为3.54秒。因此,循环花费可能占总时间的0.1%。