6

这是我的简单代码示例:

import time

t0 = time.time()
s = 0
for i in range(1000000):
    s += i
t1 = time.time()

print(s, t1 - t0)

t0 = time.time()
s = sum(i for i in range(1000000))
t1 = time.time()

print(s, t1 - t0)

在我的计算机(使用 Python 3.8)上打印:

499999500000 0.22901296615600586
499999500000 1.6930372714996338

那么,做+=一百万次比调用快 7 倍sum?这真是出乎意料。它在做什么?


编辑:我愚蠢地允许调试器附加到进程并干扰我的测量,这最终是缓慢的原因。调试器退出后,测量不再那么不可预测。正如一些答案清楚地表明,我观察到的事情不应该发生。

4

4 回答 4

5

让我们使用timeit适当的基准测试并轻松比较不同的 Python 版本,让我们在 Docker 容器中运行它:

so62514160.py

N = 1000000

def m1():
    s = 0
    for i in range(N):
        s += i

def m2():
    s = sum(i for i in range(N))

def m3():
    s = sum(range(N))

so62514160bench.sh

for image in python:2.7 python:3.6 python:3.7 python:3.8; do
    for fun in m1 m2 m3; do
        echo -n "$image" "$fun "
        docker run --rm -it -v $(pwd):/app -w /app -e PYTHONDONTWRITEBYTECODE=1 "$image" python -m timeit -s 'import so62514160 as s' "s.$fun()"
    done
done

我的机器上的结果:

python:2.7 m1 10 loops, best of 3:  43.5 msec per loop
python:2.7 m2 10 loops, best of 3:  39.6 msec per loop
python:2.7 m3 100 loops, best of 3: 17.1 msec per loop
python:3.6 m1 10 loops, best of 3:  41.9 msec per loop
python:3.6 m2 10 loops, best of 3:  46 msec per loop
python:3.6 m3 100 loops, best of 3: 17.7 msec per loop
python:3.7 m1 5 loops, best of 5:   45 msec per loop
python:3.7 m2 5 loops, best of 5:   40.7 msec per loop
python:3.7 m3 20 loops, best of 5:  17.3 msec per loop
python:3.8 m1 5 loops, best of 5:   48.2 msec per loop
python:3.8 m2 5 loops, best of 5:   44.6 msec per loop
python:3.8 m3 10 loops, best of 5:  19.2 msec per loop

阴谋

在此处输入图像描述

于 2020-06-22T12:29:16.533 回答
2

首先,您的观察可能无法很好地推广到其他系统,因为您的测量方式非常不可靠,因为它容易受到性能波动的影响,而性能波动必然取决于您的操作系统对当时波动的系统负载的反应方式的测量。你应该使用timeit或类似的东西。

例如,这是我在虚拟环境(Google Colab)中使用 Python 3.6 的时间(在其他答案中似乎可以重现):

import numba as nb


def sum_loop(n):
    result = 0
    for x in range(n):
        result += x
    return result


sum_loop_nb = nb.jit(sum_loop)
sum_loop_nb.__name__ = 'sum_loop_nb'


def sum_analytical(n):
    return n * (n - 1) // 2


def sum_list(n):
    return sum([x for x in range(n)])


def sum_gen(n):
    return sum(x for x in range(n))


def sum_range(n):
    return sum(range(n))


sum_loop_nb(10)  # to trigger compilation


funcs = sum_analytical, sum_loop, sum_loop_nb, sum_gen, sum_list, sum_range


n = 1000000
for func in funcs:
    print(func.__name__, func(n))
    %timeit func(n)
# sum_analytical 499999500000
# 10000000 loops, best of 3: 222 ns per loop
# sum_loop 499999500000
# 10 loops, best of 3: 55.6 ms per loop
# sum_loop_nb 499999500000
# 10000000 loops, best of 3: 196 ns per loop
# sum_gen 499999500000
# 10 loops, best of 3: 51.7 ms per loop
# sum_list 499999500000
# 10 loops, best of 3: 68.4 ms per loop
# sum_range 499999500000
# 100 loops, best of 3: 17.8 ms per loop

您不太可能在不同的 Python 版本中观察到不同的时间。

sum_analytical()sum_loop_nb()版本只是为了好玩而包含在内,不再进一步分析。它的sum_list()行为也与其他的完全不同,因为它为计算创建了一个很大的、很大程度上不必要的对象,而且它也没有被进一步分析。


当然,这些不同时间的原因在于所考虑的函数版本产生的字节码。特别是,from sum_loop()through sum_range()one 越来越简单的代码:

import dis

funcs = sum_loop, sum_gen, sum_range
for func in funcs:
    print(func.__name__)
    print(dis.dis(func))
    print()

# sum_loop
#   2           0 LOAD_CONST               1 (0)
#               2 STORE_FAST               1 (result)

#   3           4 SETUP_LOOP              24 (to 30)
#               6 LOAD_GLOBAL              0 (range)
#               8 LOAD_FAST                0 (n)
#              10 CALL_FUNCTION            1
#              12 GET_ITER
#         >>   14 FOR_ITER                12 (to 28)
#              16 STORE_FAST               2 (x)

#   4          18 LOAD_FAST                1 (result)
#              20 LOAD_FAST                2 (x)
#              22 INPLACE_ADD
#              24 STORE_FAST               1 (result)
#              26 JUMP_ABSOLUTE           14
#         >>   28 POP_BLOCK

#   5     >>   30 LOAD_FAST                1 (result)
#              32 RETURN_VALUE
# None

# sum_gen
#   9           0 LOAD_GLOBAL              0 (sum)
#               2 LOAD_CONST               1 (<code object <genexpr> at 0x7f86d67c49c0, file "<ipython-input-4-9519b0039c88>", line 9>)
#               4 LOAD_CONST               2 ('sum_gen.<locals>.<genexpr>')
#               6 MAKE_FUNCTION            0
#               8 LOAD_GLOBAL              1 (range)
#              10 LOAD_FAST                0 (n)
#              12 CALL_FUNCTION            1
#              14 GET_ITER
#              16 CALL_FUNCTION            1
#              18 CALL_FUNCTION            1
#              20 RETURN_VALUE
# None

# sum_range
#  13           0 LOAD_GLOBAL              0 (sum)
#               2 LOAD_GLOBAL              1 (range)
#               4 LOAD_FAST                0 (n)
#               6 CALL_FUNCTION            1
#               8 CALL_FUNCTION            1
#              10 RETURN_VALUE
# None
于 2020-06-22T12:24:53.727 回答
1

啊,我自己找到了答案,但它带来了另一个问题。

所以,这要快得多:

t0 = time.time()
s = sum(range(1000000))
t1 = time.time()

print(s, t1 - t0)

结果是:

499999500000 0.05099987983703613

因此,正如预期的那样,sum比 快+=,但生成器表达式(i for i in range(n))比其他任何东西都要慢得多。

不得不说,这也是相当令人惊讶的。

于 2020-06-22T12:17:30.587 回答
0

我得到不同的数字

python3 main.py
499999500000 0.0832064151763916
499999500000 0.03934478759765625

从这段代码

import time

to0 = []
to1 = []

for i in range(1000):
    t0 = time.time()
    s = 0
    for i in range(1000000):
        s += i
    t1 = time.time()

    to0.append(t1 - t0)

    t0 = time.time()
    s = sum(i for i in range(1000000))
    t1 = time.time()

    to1.append(t1 - t0)


print(sum(to0)/len(to0))
print(sum(to1)/len(to1))

我明白了

0.07862246823310852
0.0318267240524292

尝试更新你的 python 所有这些都在 Python 3.7.3 上运行

于 2020-06-22T12:22:45.523 回答