2015-06-12 4 views
7

Я писал игру с тик-таковым и использовал Enum для представления трех результатов - lose, draw и win. Я думал, что это будет лучше, чем использование строк ("lose", "win", "draw"), чтобы указать эти значения. Но использование enums дало мне значительный успех.Как использовать списки Python 3.4 без значительного замедления?

Вот минимальный пример, где я просто ссылаюсь либо на Result.lose, либо на литеральную строку lose.

import enum 
import timeit 
class Result(enum.Enum): 
    lose = -1 
    draw = 0 
    win = 1 

>>> timeit.timeit('Result.lose', 'from __main__ import Result') 
1.705788521998329 
>>> timeit.timeit('"lose"', 'from __main__ import Result') 
0.024598151998361573 

Это намного медленнее, чем просто ссылка на глобальную переменную.

k = 12 

>>> timeit.timeit('k', 'from __main__ import k') 
0.02403248500195332 

Мои вопросы:

  • Я знаю, что глобальные поиски гораздо медленнее, чем местные поисков в Python. Но почему запросы перечислить еще хуже?
  • Как можно эффективно использовать перечисления, не жертвуя производительностью? Поиск Enum оказался полностью доминирующим во время выполнения моей программы tic-tac-toe. Мы могли бы сохранить локальные копии перечисления в каждой функции или обернуть все в одном классе, но оба они кажутся неудобными.
+0

Я думаю, что это, вероятно, медленный поиск атрибутов. Если вы делаете что-то вроде 'lose = Result.lose', а затем проверяете« потерять », будь то локальный или глобальный, я думаю, вы увидите измеримое ускорение. – Shashank

+0

Спасибо, что работает очень хорошо. Вы знаете, почему поиск атрибутов намного медленнее, чем даже глобальный поиск? Я знаю, что местные жители хранятся в массиве с фиксированной длиной, а глобальные - в dict, но что такое сделка с атрибутами? –

+0

Не знаю, извините. И я не мог сказать вам ничего с уверенностью, не читая источник CPython. Если бы я должен был догадаться, я бы сказал, что объекты реализуются с ассоциативными массивами или картами или что-то под капотом (только возможность, а не как факт), так что может быть стоимость алгоритма хеширования, используемого в именах атрибутов которые похожи на строковые ключи на хэш-таблицу, но это все предположения. В любом случае вы теперь знаете, как минимизировать его в случае повторных поисков. Локализация ftw. – Shashank

ответ

10

Вы выбираете временной цикл. Строковый литерал сама по себе проигнорировано полностью:

>>> import dis 
>>> def f(): "lose" 
... 
>>> dis.dis(f) 
    1   0 LOAD_CONST    1 (None) 
       3 RETURN_VALUE   

Это функция, которая ничего не делает на всех. Таким образом, цикл синхронизации принимает 0.024598151998361573 секунд, чтобы запустить 1 миллион раз.

В этом случае строка фактически стала строкой документации функции f:

>>> f.__doc__ 
'lose' 

но CPython обычно опускает строковые литералы в коде, если не назначена или иначе часть выражения:

>>> def f(): 
...  1 + 1 
...  "win" 
... 
>>> dis.dis(f) 
    2   0 LOAD_CONST    2 (2) 
       3 POP_TOP    

    3   4 LOAD_CONST    0 (None) 
       7 RETURN_VALUE   

Здесь 1 + 1 сложен в константу (2), и строковый литерал снова исчез.

Таким образом, вы не можете сравнить это с поиском атрибута объекта enum. Да, поиск атрибута требует циклов. Но так же ищет другую переменную. Если вы действительно беспокоитесь о производительности, вы всегда можете кэш Поиск атрибута:

>>> import timeit 
>>> import enum 
>>> class Result(enum.Enum): 
...  lose = -1 
...  draw = 0 
...  win = 1 
... 
>>> timeit.timeit('outcome = Result.lose', 'from __main__ import Result') 
1.2259576459764503 
>>> timeit.timeit('outcome = lose', 'from __main__ import Result; lose = Result.lose') 
0.024848614004440606 

В timeit тестах все переменные являются местными жителями, так как Result и lose местные поиски.

enum атрибуты поиски действительно берут немного больше времени, чем «обычные» поиски атрибутов:

>>> class Foo: bar = 'baz' 
... 
>>> timeit.timeit('outcome = Foo.bar', 'from __main__ import Foo') 
0.04182224802207202 

Это потому, что enum метакласс включает в себя specialised __getattr__ hook, который называется каждый раз, когда вы смотрите атрибут; атрибуты класса enum рассматриваются в специализированном словаре, а не в классе __dict__. Оба выполнения этого метода крючков и дополнительный поиск атрибута (для доступа к карте) потребуются дополнительное время:

>>> timeit.timeit('outcome = Result._member_map_["lose"]', 'from __main__ import Result') 
0.25198313599685207 
>>> timeit.timeit('outcome = map["lose"]', 'from __main__ import Result; map = Result._member_map_') 
0.14024519600206986 

В игре Tic-Tac-Toe вы вообще не беспокоиться о том, что сводится к незначительным различиям синхронизации , Не тогда, когда игрок человека на несколько порядков меньше, чем ваш компьютер. Этот человеческий игрок не собирается замечать разницу между 1,2 микросекундами или 0,024 микросекундами.

+0

А, хорошо, поэтому цикл не доминирует, когда не используется перечисление. Но что объясняет разницу между ссылкой на глобальную переменную «k» и ссылкой на «Result.lose»? –

+0

@EliRose: поиск действительно стоит, да. Поэтому, когда это действительно имеет значение (например, в критически важной части вашего кода, в цикле), кешируйте поиск в локальном. –

+0

Меня больше интересуют внутренности, чем я в игре с tic-tac-toe, в которой есть много способов решить эту проблему. Почему поиск атрибутов происходит гораздо медленнее, чем глобальный поиск? –