2016-02-04 2 views
3

У меня создалось впечатление, что использование конструкции суммы было намного быстрее, чем запуск цикла for. Однако, в следующем коде для петли на самом деле работает быстрее:Скорость «суммирования» в Python

import time 

Score = [[3,4,5,6,7,8] for i in range(40)] 

a=[0,1,2,3,4,5,4,5,2,1,3,0,5,1,0,3,4,2,2,4,4,5,1,2,5,4,3,2,0,1,1,0,2,0,0,0,1,3,2,1] 

def ver1(): 
    for i in range(100000): 
     total = 0 
     for j in range(40): 
      total+=Score[j][a[j]] 
    print (total) 

def ver2(): 
    for i in range(100000): 
     total = sum(Score[j][a[j]] for j in range(40)) 
    print (total) 


t0 = time.time() 
ver1() 
t1 = time.time() 
ver2() 
t2 = time.time() 

print("Version 1 time: ", t1-t0) 
print("Version 2 time: ", t2-t1) 

Выход:

208 
208 
Version 1 time: 0.9300529956817627 
Version 2 time: 1.066061019897461 

Я делаю что-то не так? Есть ли способ сделать это быстрее?

(Обратите внимание, что это только демо я создал, в моем реальном приложении баллы не будут повторяться таким образом)

Некоторая дополнительная информация: Это работает на Python 3.4.4 64-бит, на Windows 7 64-бит, на i7.

+0

[этот вопрос] (http: // stackoverflow.com/questions/24578896/python-built-in-sum-function-vs-for-loop-performance) говорит, что 'sum' должен быть намного быстрее, чем цикл' for'. – Barmar

+0

Я думаю, что ваше узкое место - это понимание списка, а не дополнение. – Barmar

+1

@Barmar В любой из функций нет понимания списка. Почему должно быть понимание * генератора * узким местом, поскольку оно делает почти то же самое, что и цикл for? Я думаю, что это может быть накладные расходы на вызовы функций 'sum', потому что диапазон настолько мал ... – L3viathan

ответ

1

Поскольку J итерация по обоим спискам, я думал, что доживу, если почтовый работал лучше:

def ver3(): 
    for i in range(100000): 
     total = sum(s[i] for s,i in zip(Score,a)) 
    print (total) 

На py2 это работает около 30% медленнее, чем версия 2, но на PY3 около 20% быстрее чем версия 1. Если меняю zip в izip (импорт из itertools), это сокращает время до между версиями 1 и 2.

+1

Heck, если вы хотите получить умный и удалить все байт-код Python из уравнения во время 'sum':' from operator import getitem', 'total = sum (map (getitem, Score, a))' вероятно, сделает еще лучше (на Py2, используйте 'itertools.imap', чтобы избежать промежуточного' list'). – ShadowRanger

2

Это, кажется, зависит от системы, вероятно, версии питона. В моей системе, разница составляет около 13%:

python sum.py 
208 
208 
('Version 1 time: ', 0.6371259689331055) 
('Version 2 time: ', 0.7342419624328613) 

Две версии не измеряет sum по сравнению с ручным зацикливанием, так как петля «тело» не является идентичным. ver2 работает больше, потому что он создает выражение генератора 100000 раз, в то время как тело цикла ver1 почти тривиально, но оно создает список из 40 элементов для каждой итерации. Вы можете изменить пример, чтобы быть идентичными, и тогда вы увидите эффект sum:

def ver1(): 
    r = [Score[j][a[j]] for j in range(40)] 
    for i in xrange(100000): 
     total = 0 
     for j in r: 
      total+=j 
    print (total) 

def ver2(): 
    r = [Score[j][a[j]] for j in xrange(40)] 
    for i in xrange(100000): 
     total = sum(r) 
    print (total) 

Я переместил все из тела внутреннего цикла и из него sum вызова, чтобы убедиться, что мы измеряем только накладные расходы на ручные петли. Использование xrange вместо range дополнительно улучшает общее время выполнения, но это относится к обеим версиям и, таким образом, не меняет сравнение. Результаты модифицированного кода на моей системе:

python sum.py 
208 
208 
('Version 1 time: ', 0.2034609317779541) 
('Version 2 time: ', 0.04234910011291504) 

ver2 в пять раз быстрее, чем ver1. Это чистый прирост производительности при использовании sum вместо ручного инструмента.

Вдохновленный ShadowRanger's comment on the question about lookups, я модифицировал пример для сравнения исходного кода и проверьте, если поиск связанных символов:

def gen(s,b): 
    for j in xrange(40): 
     yield s[j][b[j]] 

def ver2(): 
    for i in range(100000): 
     total = sum(gen(Score, a)) 
    print (total) 

создать небольшой собственный генератор, который локально связывает Score и a для предотвращения дорогостоящей Lookups в родительских областях. Выполнение:

python sum.py 
208 
208 
('Version 1 time: ', 0.6167840957641602) 
('Version 2 time: ', 0.6198039054870605) 

Один и тот же поисковый запрос составляет ~ 12% от времени выполнения.

+0

Я не думаю, что это справедливое сравнение. Суть заключается в том, чтобы найти самый быстрый способ вычисления суммы, и, удалив промежуточный список из цикла, вы по существу предварительно вычислили часть суммы и только показали, что использование «суммы» в плоском списке происходит быстрее, чем повторение. – CaptainCodeman

+1

@CaptainCodeman Если вы хотите сравнить скорость 'sum' против контура, написанного hadn, вы должны взять все остальное. В противном случае вы сравниваете яблоки и апельсины. В противном случае я бы решительно утверждал, что ваш код для ver2 является субоптимальным. – Jens

+0

Если вы просто хотите суммировать плоский массив, то уверен, что «сумма» выполняется быстрее, но это никогда не подвергалось сомнению. Это проверяет его на более сложное приложение, которое может возникнуть. На практике нет возможности сгладить массив. – CaptainCodeman

Смежные вопросы