Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python itertools.product比嵌套for循环慢_Python_Performance_Itertools - Fatal编程技术网

Python itertools.product比嵌套for循环慢

Python itertools.product比嵌套for循环慢,python,performance,itertools,Python,Performance,Itertools,我正在尝试使用itertools.product函数使我的代码片段(在同位素模式模拟器中)更容易阅读,希望也更快(即没有创建中间结果的状态),但是,我使用cProfiling库测试了两个版本的代码,并注意到itertools.product比嵌套for循环慢得多 用于测试的示例值: carbons = [(0.0, 0.004613223957020534), (1.00335, 0.02494768843632857), (2.0067, 0.0673219412049374), (3.010

我正在尝试使用
itertools.product
函数使我的代码片段(在同位素模式模拟器中)更容易阅读,希望也更快(即没有创建中间结果的状态),但是,我使用
cProfiling
库测试了两个版本的代码,并注意到
itertools.product
比嵌套for循环慢得多

用于测试的示例值:

carbons = [(0.0, 0.004613223957020534), (1.00335, 0.02494768843632857), (2.0067, 0.0673219412049374), (3.0100499999999997, 0.12087054681917497), (4.0134, 0.16243239687902825), (5.01675, 0.17427700732161705), (6.020099999999999, 0.15550695260604208), (7.0234499999999995, 0.11869556397525197), (8.0268, 0.07911287899598853), (9.030149999999999, 0.04677626606764402)]
hydrogens = [(0.0, 0.9417611429667746), (1.00628, 0.05651245007201512)]
nitrogens = [(0.0, 0.16148864310897554), (0.99703, 0.2949830688288726), (1.99406, 0.26887643366755537), (2.99109, 0.16305943261399866), (3.98812, 0.0740163089529218), (4.98515, 0.026824040474519875), (5.98218, 0.008084687617425748)]
oxygens17 = [(0.0, 0.8269292736927519), (1.00422, 0.15717628899143962), (2.00844, 0.014907548827832968)]
oxygens18 = [(0.0, 0.3584191873916266), (2.00425, 0.36813434247849824), (4.0085, 0.18867830334103902), (6.01275, 0.06433912182670033), (8.017, 0.016421642936302827)]
sulfurs33 = [(0.0, 0.02204843659673093), (0.99939, 0.08442569434459646), (1.99878, 0.16131398792444965), (2.99817, 0.2050722764666321), (3.99756, 0.1951327596407101), (4.99695, 0.14824112268069747), (5.99634, 0.09365899226198841), (6.99573, 0.050618028523695714), (7.99512, 0.023888506307006133), (8.99451, 0.010000884811585533)]
sulfurs34 = [(0.0, 3.0106350597190195e-10), (1.9958, 6.747270089956428e-09), (3.9916, 7.54568412614702e-08), (5.9874, 5.614443102700176e-07), (7.9832, 3.1268212758750728e-06), (9.979, 1.3903197959791067e-05), (11.9748, 5.141248916434075e-05), (13.970600000000001, 0.0001626288218672788), (15.9664, 0.00044921518047309414), (17.9622, 0.0011007203440032396)]
sulfurs36 = [(0.0, 0.904828368500412), (3.99501, 0.0905009370374487)]
演示嵌套for循环的代码段:

totals = []
for i in carbons:
    for j in hydrogens:
        for k in nitrogens:
            for l in oxygens17:
                for m in oxygens18:
                    for n in sulfurs33:
                        for o in sulfurs34:
                            for p in sulfurs36:
                                totals.append((i[0]+j[0]+k[0]+l[0]+m[0]+n[0]+o[0]+p[0], i[1]*j[1]*k[1]*l[1]*m[1]*n[1]*o[1]*p[1]))
演示使用
itertools.product
的代码片段:

totals = []
for i in itertools.product(carbons,hydrogens,nitrogens,oxygens17,oxygens18,sulfurs33,sulfurs34,sulfurs36):
    massDiff = i[0][0]
    chance = i[0][1]
    for j in i[1:]:
        massDiff += j[0]
        chance = chance * j[1]
    totals.append((massDiff,chance))
分析结果(基于每个方法10次运行)对于嵌套for循环方法平均约0.8秒,对于
itertools.product
方法平均约1.3秒。因此,我的问题是,我是否错误地使用了
itertools.product
函数,还是应该坚持使用嵌套的for循环

--更新--

我已经包括了两个我的
cProfile
结果:

# ITERTOOLS.PRODUCT APPROACH 
420003 function calls in 1.306 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.018    0.018    1.306    1.306 <string>:1(<module>)
        1    1.246    1.246    1.289    1.289 IsotopeBas.py:64(option1)
   420000    0.042    0.000    0.042    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
#ITERTOOLS.PRODUCT方法
420003个函数调用只需1.306秒
订购人:标准名称
ncalls tottime percall cumtime percall文件名:lineno(函数)
1    0.018    0.018    1.306    1.306 :1()
1 1.246 1.246 1.289 1.289同位素基数:64(选项1)
420000 0.042 0.000 0.042 0.000{“列表”对象的“附加”方法}
1 0.000 0.000 0.000 0.000{方法'disable'的''lsprof.Profiler'对象}
以及:

#嵌套循环方法
420003次函数调用(0.830秒)
订购人:标准名称
ncalls tottime percall cumtime percall文件名:lineno(函数)
1    0.019    0.019    0.830    0.830 :1()
1 0.769 0.769 0.811 0.811同位素基数:78(选项2)
420000 0.042 0.000 0.042 0.000{“列表”对象的“附加”方法}
1 0.000 0.000 0.000 0.000{方法'disable'的''lsprof.Profiler'对象}

我强烈怀疑,这种缓慢性来自于每次通过
lambda
创建临时变量/就地添加/创建函数以及函数调用的开销。为了说明为什么在案例2中加法的速度较慢,我这样做了:

import dis
s = '''
    a = (1, 2)
    b = (2, 3)
    c = (3, 4)

    z = (a[0] + b[0] + c[0])

    t = 0
    t += a[0]
    t += b[0]
    t += c[0]
    '''

x = compile(s, '', 'exec')

dis.dis(x)
这使得:

<snip out variable declaration>
5          18 LOAD_NAME                0 (a)
           21 LOAD_CONST               4 (0)
           24 BINARY_SUBSCR
           25 LOAD_NAME                1 (b)
           28 LOAD_CONST               4 (0)
           31 BINARY_SUBSCR
           32 BINARY_ADD
           33 LOAD_NAME                2 (c)
           36 LOAD_CONST               4 (0)
           39 BINARY_SUBSCR
           40 BINARY_ADD
           41 STORE_NAME               3 (z)

正如您所看到的,由于
+=
添加与内联添加相比,增加了2个操作码开销。这种开销来自于需要加载和存储名称。我想这只是一个开始,Antti Haapala的代码花在调用c代码的cpython内置程序上的时间比只在python中运行的时间要多。函数调用开销在python中非常昂贵。

您最初的itertool代码在不必要的
lambda中花费了大量额外的时间,并且手动构建中间值列表-其中许多可以用内置功能替代

现在,内部for循环确实增加了相当多的额外开销:只需尝试以下几点,性能与原始代码非常相似:

for a in itertools.product(carbons,hydrogens,nitrogens,oxygens17,
                           oxygens18,sulfurs33,sulfurs34,sulfurs36):
    i, j, k, l, m, n, o, p = a
    totals.append((i[0]+j[0]+k[0]+l[0]+m[0]+n[0]+o[0]+p[0],
                   i[1]*j[1]*k[1]*l[1]*m[1]*n[1]*o[1]*p[1]))

下面的代码尽可能多地在CPython内置端运行,我测试了它与代码的等效性。值得注意的是,代码使用
zip(*iterable)
解压每个产品结果;然后使用
reduce
运算符.mul
进行乘积运算,使用
sum
进行求和运算;2台发电机,用于浏览列表。for循环仍然有轻微的节拍,但是硬编码的for循环可能不是长期使用的

import itertools
from operator import mul
from functools import partial

prod = partial(reduce, mul)
elems = carbons, hydrogens, nitrogens, oxygens17, oxygens18, sulfurs33, sulfurs34, sulfurs36
p = itertools.product(*elems)

totals = [
    ( sum(massdiffs), prod(chances) )
    for massdiffs, chances in
    ( zip(*i) for i in p )
]

我对这两个函数进行了计时,它们使用了绝对最少的额外代码:

def nested_for(first_iter, second_iter):
    for i in first_iter:
        for j in second_iter:
            pass

def using_product(first_iter, second_iter):
    for i in product(first_iter, second_iter):
        pass
它们的字节码指令类似:

dis(nested_for)
  2           0 SETUP_LOOP              26 (to 28)
              2 LOAD_FAST                0 (first_iter)
              4 GET_ITER
        >>    6 FOR_ITER                18 (to 26)
              8 STORE_FAST               2 (i)

  3          10 SETUP_LOOP              12 (to 24)
             12 LOAD_FAST                1 (second_iter)
             14 GET_ITER
        >>   16 FOR_ITER                 4 (to 22)
             18 STORE_FAST               3 (j)

  4          20 JUMP_ABSOLUTE           16
        >>   22 POP_BLOCK
        >>   24 JUMP_ABSOLUTE            6
        >>   26 POP_BLOCK
        >>   28 LOAD_CONST               0 (None)
             30 RETURN_VALUE

dis(using_product)
  2           0 SETUP_LOOP              18 (to 20)
              2 LOAD_GLOBAL              0 (product)
              4 LOAD_FAST                0 (first_iter)
              6 LOAD_FAST                1 (second_iter)
              8 CALL_FUNCTION            2
             10 GET_ITER
        >>   12 FOR_ITER                 4 (to 18)
             14 STORE_FAST               2 (i)

  3          16 JUMP_ABSOLUTE           12
        >>   18 POP_BLOCK
        >>   20 LOAD_CONST               0 (None)
             22 RETURN_VALUE
结果如下:

>>> timer = partial(timeit, number=1000, globals=globals())
>>> timer("nested_for(range(100), range(100))")
0.1294467518782625
>>> timer("using_product(range(100), range(100))")
0.4335527486212385

通过
timeit
和手动使用
perf_计数器
执行的附加测试结果与上述结果一致。对于
循环,使用
产品
显然比使用嵌套的
要慢得多。然而,根据前面答案中已经显示的测试,两种方法之间的差异与嵌套循环的数量成反比(当然,还有包含笛卡尔乘积的元组的大小)。

您能给出一些碳、氢、氮、氧17、氧18、硫33的样本值吗,sulfurs34,sulfurs36
,这样我们就可以重现这一点,并确认这不是Python中时间代码的速度。使用我将用
timeit
重试,我将向OP中添加一些示例列表(对于太大的列表表示歉意)。您正在使用append来制造机会,并重新运行loop reduce,即使您可以在for中成倍增加,等等。我确实这样做了。我已经调整了itertools代码片段,以更正您所描述的内容,但是仍然较慢。我已经删除了lambda代码块(这有点傻,我不得不同意)。你会说(在我现在的代码片段中)剩下的差异(大约0.5秒)来自中间值吗?我的代码给出了0.788对0.562的循环,因此,我猜不会太快。这种差异已经比我最初观察到的要小很多,但我仍然对
itertools.product
函数在性能上的显著差异感到惊讶。我不得不说,我不知道是什么使
itertools
变体速度变慢由于reduce已从代码中删除,因此与嵌套for循环相比,根据
cProfiler
的唯一区别来自分析输出中的第二行(这说明不了多少)。我真的很喜欢你如何摆脱我在那里的额外for循环,而且这个实现确实使性能差异变得微不足道(两个变体之间),干杯。
dis(nested_for)
  2           0 SETUP_LOOP              26 (to 28)
              2 LOAD_FAST                0 (first_iter)
              4 GET_ITER
        >>    6 FOR_ITER                18 (to 26)
              8 STORE_FAST               2 (i)

  3          10 SETUP_LOOP              12 (to 24)
             12 LOAD_FAST                1 (second_iter)
             14 GET_ITER
        >>   16 FOR_ITER                 4 (to 22)
             18 STORE_FAST               3 (j)

  4          20 JUMP_ABSOLUTE           16
        >>   22 POP_BLOCK
        >>   24 JUMP_ABSOLUTE            6
        >>   26 POP_BLOCK
        >>   28 LOAD_CONST               0 (None)
             30 RETURN_VALUE

dis(using_product)
  2           0 SETUP_LOOP              18 (to 20)
              2 LOAD_GLOBAL              0 (product)
              4 LOAD_FAST                0 (first_iter)
              6 LOAD_FAST                1 (second_iter)
              8 CALL_FUNCTION            2
             10 GET_ITER
        >>   12 FOR_ITER                 4 (to 18)
             14 STORE_FAST               2 (i)

  3          16 JUMP_ABSOLUTE           12
        >>   18 POP_BLOCK
        >>   20 LOAD_CONST               0 (None)
             22 RETURN_VALUE
>>> timer = partial(timeit, number=1000, globals=globals())
>>> timer("nested_for(range(100), range(100))")
0.1294467518782625
>>> timer("using_product(range(100), range(100))")
0.4335527486212385