Разница в производительности при вычислении среднего значения

Столкнулся с интересной ситуацией. Написал код, чтобы проверить, так ли это.

Нам надо посчитать среднее арифметическое некоторых значений. Условие в том, что мы не знаем, сколько значений у нас будет в итоге. И прикол в том, что добавлять числа в массив и считать сумму массива после быстрее, чем считать сумму сразу, без добавления в массив.

import time
t0 = time.time()
for j in range(100000):
    sm = 0
    cn = 0
    for i in range(150):
        cn += 1
        sm += i * (i + 1)
    l = sm / cn
print('%.6f' % (time.time() - t0))
t0 = time.time()
for j in range(100000):
    a = []
    for i in range(150):
        a.append(i * (i + 1))
    l = sum(a) / len(a)
print('%.6f' % (time.time() - t0))

Вывод такой:

2.146408 - с суммой

1.838875 - с массивом

Вопрос, а почему второе быстрее?


Ответы (3 шт):

Автор решения: Эникейщик

Потому что в первом случае на одно действие больше: ещё есть cn += 1. На это тоже тратится время.

→ Ссылка
Автор решения: MaxU

Если для вас важна скорость выполнение - воспользуйтесь Numpy:

import numpy as np  #  pip install numpy

def orig1(N):
    sm = 0
    cn = 0
    for i in range(N):
        cn += 1
        sm += i * (i + 1)
    return sm / cn

def orig2(N):
    a = []
    for i in range(N):
        a.append(i * (i + 1))
    return sum(a) / len(a)

def np1(N):
    return np.mean(np.arange(N) * np.arange(1, N+1))

сначала проверим правильность вычислений:

In [302]: orig1(150)
Out[302]: 7499.666666666667

In [303]: orig2(150)
Out[303]: 7499.666666666667

In [304]: np1(150)
Out[304]: 7499.666666666667

теперь скорость:

In [305]: %timeit orig1(150)
13.5 µs ± 71.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [306]: %timeit orig2(150)
13.3 µs ± 68.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [307]: %timeit np1(150)
7.72 µs ± 109 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Numpy покажет себя во всей красе для более крупных массивов - возьмем 1500 элементов вместо 150:

In [308]: %timeit orig1(1500)
177 µs ± 2.51 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [309]: %timeit orig2(1500)
153 µs ± 1.03 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [310]: %timeit np1(1500)
10.7 µs ± 147 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

тут уже разница не в разы, а на порядки)

→ Ссылка
Автор решения: passant

Проверяем тезис о лишней операции cn += 1 в первом опыте:

import time
t0 = time.time()
for j in range(100000):
    sm = 0
    cn = 0
    for i in range(150):
        cn += 1
        sm += i * (i + 1)
    l = sm / cn
print('%.6f' % (time.time() - t0))
t0 = time.time()
for j in range(100000):
    sm = 0
    cn = 0
    for i in range(150):
        sm += i * (i + 1)
    l = sm / (i+1)
print('%.6f' % (time.time() - t0))
t0 = time.time()
for j in range(100000):
    a = []
    for i in range(150):
        a.append(i * (i + 1))
    l = sum(a) / len(a)
print('%.6f' % (time.time() - t0))

Результаты соответственно:

2.877234

2.094582

2.381735

Т.е. убрали ненужную операцию, и скорость сразу стала лучше чем в вашем втором примере.

→ Ссылка