Вопросы обобщены здесь. Да, я знаю некоторые из этих ответов;) и я могу помахать рукой над другими, но я бы очень хотел перейти к деталям -Гритти здесь.
I только что посмотрел PyCon 2011: Как это сделал Dropbox и как Python помог (правда, я пропустил большую часть частей), но НАКОНЕЦ действительно интересный материал начался примерно в 22:23.
Выступающий высказался за создание ваших внутренних циклов на C, и этот «запускать один раз» не требует особой оптимизации (имеет смысл) ... затем он продолжает утверждать ... перефразируя:
Передайте композицию из итераторы в любой для значительного повышения скорости.
Вот код (надеюсь, он идентичный):
import itertools, hashlib, time
_md5 = hashlib.md5()
def run():
for i in itertools.repeat("foo", 10000000):
_md5.update(i)
a = time.time(); run(); time.time() - a
Out[118]: 9.44077205657959
_md5 = hashlib.md5()
def run():
any(itertools.imap(_md5.update, itertools.repeat("foo", 10000000)))
a = time.time(); run(); time.time() - a
Out[121]: 6.547091007232666
Хмм, похоже, для еще большего увеличения скорости я могу просто получить более быстрый компьютер! (Судя по его слайду.)
Затем он несколько раз машет руками, не вдаваясь в подробности , почему .
Я уже знал об итераторах из ответа на питонический способ сделать что-то N раз без индексной переменной? благодаря Алексу Мартелли.
Тогда я подумал: интересно, действительно ли карта добавляет улучшения скорости? Моя последняя мысль была WTF ??? переход на любой ? В САМОМ ДЕЛЕ??? Конечно, это не может быть правильным, поскольку в документации любой определяется как:
def any(iterable):
for element in iterable:
if element:
return True
return False
Почему в мире передача итераторов в любой делает мой код быстрее?
Затем я протестировал его, используя следующее (среди многих другие тесты), но вот что меня подводит:
def print_true(x):
print 'True'
return 'Awesome'
def test_for_loop_over_iter_map_over_iter_repeat():
for result in itertools.imap(print_true, itertools.repeat("foo", 5)):
pass
def run_any_over_iter_map_over_iter_repeat():
any(itertools.imap(print_true, itertools.repeat("foo", 5)))
And the runs:
In [67]: test_for_loop_over_iter_map_over_iter_repeat()
True
True
True
True
True
In [74]: run_any_over_iter_map_over_iter_repeat()
True
К стыду. Я заявил, что этот ПАРЕНЬ ЕГО ПОЛНЫЙ. Еретик ! Но я успокоился и продолжил тестировать. Если бы это было правдой, как, черт возьми, Dropbox мог бы вообще работать!?!?
И после дальнейшего тестирования он действительно работал ... Сначала я просто использовал простой объект счетчика, и он насчитал до 10 000 000 в обоих случаях.
Итак, вопрос в том, почему мой объект Counter работал, а моя функция print_true с треском провалилась?
class Counter(object):
count = 0
def count_one(self, none):
self.count += 1
def run_any_counter():
counter = Counter()
any(itertools.imap(counter.count_one, itertools.repeat("foo", 10000000)))
print counter.count
def run_for_counter():
counter = Counter()
for result in itertools.imap(counter.count_one, itertools.repeat("foo", 10000000)):
pass
print counter.count
вывод:
%time run_for_counter()
10000000
CPU times: user 5.54 s, sys: 0.03 s, total: 5.57 s
Wall time: 5.68 s
%time run_any_counter()
10000000
CPU times: user 5.28 s, sys: 0.02 s, total: 5.30 s
Wall time: 5.40 s
WTF еще больше даже после удаления ненужного аргумента и написания наиболее разумного кода для моего объекта Counter, он ВСЕ ЕЩЕ медленнее, чем версия с любой картой. Где моя морковь?!?:
class CounterNoArg(object):
count = 0
def count_one(self):
self.count += 1
def straight_count():
counter = CounterNoArg()
for _ in itertools.repeat(None, 10000000):
counter.count_one()
print counter.count
Ouput:
In [111]: %time straight_count()
10000000
CPU times: user 5.44 s, sys: 0.02 s, total: 5.46 s
Wall time: 5.60 s
Я спрашиваю, потому что думаю, что питонистам или питонистам нужна морковь, поэтому мы не начинаем передавать вещи any или all для увеличения производительности, или он уже существует? Возможно, эквивалент itertools.imap , который будет просто вызывать функцию снова и снова, и, возможно, определенное количество раз.
Лучшее, что мне удалось, это (использование списка дает интересные результаты):
def super_run():
counter = CounterNoArg()
for _ in (call() for call in itertools.repeat(counter.count_one, 10000000)):
pass
print counter.count
def super_counter_run():
counter = CounterNoArg()
[call() for call in itertools.repeat(counter.count_one, 10000000)]
print counter.count
def run_any_counter():
counter = Counter()
any(itertools.imap(counter.count_one, itertools.repeat("foo", 10000000)))
print counter.count
%time super_run()
10000000
CPU times: user 5.23 s, sys: 0.03 s, total: 5.26 s
Wall time: 5.43 s
%time super_counter_run()
10000000
CPU times: user 4.75 s, sys: 0.18 s, total: 4.94 s
Wall time: 5.80 s
%time run_any_counter()
10000000
CPU times: user 5.15 s, sys: 0.06 s, total: 5.21 s
Wall time: 5.30 s
def run_any_like_presentation():
any(itertools.imap(_md5.update, itertools.repeat("foo", 10000000)))
def super_run_like_presentation():
[do_work for do_work in itertools.imap(_md5.update, itertools.repeat("foo", 10000000))]
def super_run_like_presentation_2():
[_md5.update(foo) for foo in itertools.repeat("foo", 10000000)]
%time run_any_like_presentation()
CPU times: user 5.28 s, sys: 0.02 s, total: 5.29 s
Wall time: 5.47 s
%time super_run_like_presentation()
CPU times: user 6.14 s, sys: 0.18 s, total: 6.33 s
Wall time: 7.56 s
%time super_run_like_presentation_2()
CPU times: user 8.44 s, sys: 0.22 s, total: 8.66 s
Wall time: 9.59 s
Ух ...
Примечание: я рекомендую вам провести тесты самостоятельно.