import time
lst = [-3, 3, 7, 0, -10, 23, -9, -8, -5, -10, 9, 3,
-2, 8, -3, 6, -1, 0, 10, -1, -6, -6, 10, -7, 3, 8,
0, 7, 1, 5, -3, -6, 4, 6, -6, -4, -3, 10, 10, -5,
-7, 0, -4, -8, 2, 9, 0, -10, -3, 3, -4, 9, -7, -8,
0, -1, 1, 7, 2, -1, 3, 0, 9, -9, 4, 7, 6, 10, 8,
-6, 3, 1, 1, 9, -8, -8, 2, 4, 10, 1, 5, -1, -1, 5,
-9, 9, -3, 3, 0, -6, 2, 5, 10, 10, 5, -6, -10, -2, -9, 'СТРОКА']
start1 = time.clock()
a = all(isinstance(item, (int, float, complex)) for item in lst)
finish1 = time.clock()
print("Результат: {}, время:{:.2}ms".format(a, (finish1 - start1) * 1000))
start2 = time.clock()
def is_numbers(iterable):
for item in iterable:
if not isinstance(item, (int, float, complex)):
return False
return True
b = is_numbers(lst)
finish2 = time.clock()
print("Результат: {}, время:{:.2}ms".format(b, (finish2 - start2) * 1000))
Результат:
>>>
====== RESTART: /home/dzmitry/adasdsadasdasdsad.py ======
Результат: False, время:0.081ms
Результат: False, время:0.042ms
>>>
====== RESTART: /home/dzmitry/adasdsadasdasdsad.py ======
Результат: False, время:0.085ms
Результат: False, время:0.043ms
>>>
Функция all() эквивалентна
def all(iterable):
for element in iterable:
if not element:
return False
return True
Получается, что первый и второй способ равнозначны, за исключением использования генераторного выражения. Почему первый способ медленнее в 1.5-2 раза?
Ответ
Дело не в проверке типов—разница во времени сохраняется даже если заменить isinstance(item, ..) вызов на f(item), где f функция ничего не делает, а просто возвращает True значение: f = lambda item: True (чтобы all() весь lst список просматривала без раннего выхода). Более того, разница остаётся, если вообще вызов функции убрать:
In [1]: %timeit all(True for _ in range(1000))
10000 loops, best of 3: 69.7 µs per loop
In [2]: def loop():
...: for _ in range(1000):
...: if not True:
...: return False
...: return True
...:
In [3]: %timeit loop()
10000 loops, best of 3: 30.3 µs per loop
Явный for-цикл может быть эффективней генератора в CPython (результат также может зависеть от платформы).
all_(), реализованная вручную, слегка медленнее встроенной версии all(), которая реализует похожий цикл в C
def all_(iterable):
for item in iterable:
if not item:
return False
return True
то есть важно что генератор используется, а не то как all() реализована:
In [4]: %timeit all_(range(1, 1000))
10000 loops, best of 3: 41.1 µs per loop
In [5]: %timeit all_(i for i in range(1, 1000))
10000 loops, best of 3: 93.9 µs per loop
Даже генератор списков (listcomp—списковое включение) может быть эффективней эквивалентного genexpr (хотя в Jython это с точностью до наоборот):
In [6]: %timeit [True for _ in range(1000)]
10000 loops, best of 3: 44.4 µs per loop
In [7]: %timeit list(True for _ in range(1000))
10000 loops, best of 3: 83.2 µs per loop
Хотя между этими выражениями не должно быть особой разницы в Питоне 3: Why this list comprehension is faster than equivalent generator expression?
Байт-код ничего особо подозрительного не показывает:
>>> import dis
>>> dis.dis(lambda: [True for _ in range(1000000)])
1 0 LOAD_CONST 1 ( at 0x610efc445eea, file "
at 0x610efc4bc0b2, file "
Первый фрагмент: создаёт функцию из listcomp объекта и вызывает её с результатом iter(range(1000000)). Второй фрагмент: создаёт функцию из genexpr и вызывает её с тем же аргументом, дополнительно вызывается list() функция с результатом.
listcomp реализуется достаточно прямолинейно:
>>> code = compile('[True for _ in range(1000000)]', ' at 0x610efc445f2c, file "
Что близко к:
def listcomp(it):
L = []
for _ in it:
L.append(True)
return L
где it = iter(range(1000000)), полученный ранее.
genexpr выглядит похоже:
>>> code = compile('list(True for _ in range(1000000))', ' at 0x610efc44145d, file "
Что близко к:
def genexpr(it):
for _ in it:
yield True
созданный генератор (объект) передаётся во встроеннуюlist() функцию, которая реализована вызовом listextend(), что выполняет код близкий к listcomp(it), приведённому выше. Снова разница в производительности не в использовании list(), а в том что ей передан генератор (list(range(1000)) и list(iter(range(1000)) могут быть быстрее как list(i for i in range(1000)) так и [True for _ in range(1000)]).
Уже не так удивительно, что даже list([True for _ in range(1000)]), который казалось бы выполняет лишнюю работу, может быть быстрее list(True for _ in range(1000)) (CPython/Pypy 2/3, Ubuntu).
Не стоит слишком увлекаться микрооптимизациями—пишите самый простой и понятный код, который решает поставленную задачу. Не изменяйте код, пока измерения не показали, что это необходимо в вашем конкретном случае.
Комментариев нет:
Отправить комментарий