10 юни 2011

Малко python benchmarking

Категория: DevLucho @ 10:45

Наскоро се сблъсках със следния казус:

Как най-ефективно да преобразувам списък от repr на двойки елементи в списък от двойки елементи (т.е. да eval-на всеки string в python-ски tuple). Пример:


["('text1', 'text2')", "('text3', 'text4')", ... "('textn', 'textm')"]

Ето и четирите начина, които тествах:


from time import time
import re

def time_it(n, func, data):
 start = time()
 out = map(func, data)
 print 'time for case %d:' % n, time()-start
 return out

data = map(repr, zip(map(str, range(10**6)), map(str, range(10**6))))
out = []

# 1 - eval на всеки елемент
out.append(time_it(1, eval, data))

# 2 - премахване на ненужните символи
out.append(time_it(2, lambda x: x[2:-2].split('\', \''), data))

# 3 - match-ване на стойностите с регулярен израз за всеки елемент
pattern = re.compile(r'''\('(\w+)', '(\w+)'\)''')
out.append(time_it(3, lambda x: pattern.search(x).group(1, 2), data))

# 4 - match-ване на стойностите с регулярен израз върху всички елементи обединени в един string
pattern = re.compile(r"'(\w+)'")
start = time()
res = pattern.findall(''.join(data))
out.append(zip(res[::2], res[1::2]))
print 'time for case 4:', time()-start

print 'are they all the same?', len(filter(lambda o: o != out[0], map(lambda x: map(tuple, x), out))) == 0

Ето и времената:


time for case 1: 15.3229999542
time for case 2: 2.14499998093
time for case 3: 1.76999998093
time for case 4: 1.20600008965
are they all the same? True

Познах им класацията по бързодействие още преди да ги напиша :).

Естествено, най-правилният начин е този, който първи идва в главата на човек – eval. За съжаление обаче, ще трябва доста да почакате! Дори и да се направите на хитри и да пробвате със следния код (който е още по-грозен от вариант номер 4)


eval(repr(data).replace('"', ''))

пак времето нужно за изпълнение е около 10 пъти повече от вариант номер 4.

Макар че четвъртият пример е най-ефективен, не го препоръчвам поради ред причини, но най-вече защото кодът не се ръководи по форматa на данните, а просто агрегира всичко, което е между кавички. За това пък третият вариант е доста близък по време до четвъртия и за разлика от него се ръководи по структурата на данните, не прави един огромен текстов низ и е доста по-кратък и разбираем. Вариант номер 2 го забравете, че съществува ;-)

Та, в заключение – гледайте да използвате по-рядко eval и да балансирате добре между разбираемост и бързодействие.

P.S. Eval is Evil by default… so never use it for evaluating external code or else your program will execute someone else’s code :)


Тагове: , ,