Ответ 1
(Это ответ на код, который теперь был отредактирован из исходного вопроса). Вы забыли вызвать функции во втором случае. Делая соответствующие модификации, результаты ожидаемые:
test1 = """
def foo1():
my_set1 = set((1, 2, 3))
foo1()
"""
timeit(test1)
# 0.48808742000255734
test2 = """
def foo2():
my_set2 = {1,2,3}
foo2()
"""
timeit(test2)
# 0.3064506609807722
Теперь причина разницы во времени заключается в том, что set()
- это вызов функции, требующий поиска в таблице символов, тогда как конструкция набора {...}
является артефактом синтаксиса и намного быстрее.
Разница очевидна при просмотре разобранного байтового кода.
import dis
dis.dis("set((1, 2, 3))")
1 0 LOAD_NAME 0 (set)
2 LOAD_CONST 3 ((1, 2, 3))
4 CALL_FUNCTION 1
6 RETURN_VALUE
dis.dis("{1, 2, 3}")
1 0 LOAD_CONST 0 (1)
2 LOAD_CONST 1 (2)
4 LOAD_CONST 2 (3)
6 BUILD_SET 3
8 RETURN_VALUE
В первом случае вызов функции выполняется инструкцией CALL_FUNCTION
для кортежа (1, 2, 3)
(которая также имеет свои собственные издержки, хотя и незначительные - она загружается как константа через LOAD_CONST
), тогда как во втором инструкция - это просто вызов BUILD_SET
, который более эффективен.
Re: ваш вопрос относительно времени, необходимого для построения кортежа, мы видим, что это на самом деле незначительно:
timeit("""(1, 2, 3)""")
# 0.01858693000394851
timeit("""{1, 2, 3}""")
# 0.11971827200613916
Кортежи являются неизменяемыми, поэтому компилятор оптимизирует эту операцию, загружая ее как константу - это называется постоянным свертыванием (вы можете ясно увидеть это из инструкции LOAD_CONST
выше), поэтому время, затрачиваемое на это, незначительно. С наборами это не видно, поскольку они изменчивы (спасибо @user2357112 за указание на это).
Для больших последовательностей мы видим похожее поведение. Синтаксис {..}
работает быстрее при построении множеств с использованием определений множеств, в отличие от set()
который должен строить множество из генератора.
timeit("""set(i for i in range(10000))""", number=1000)
# 0.9775058150407858
timeit("""{i for i in range(10000)}""", number=1000)
# 0.5508635920123197
Для справки, вы также можете использовать повторяемую распаковку на более поздних версиях:
timeit("""{*range(10000)}""", number=1000)
# 0.7462548640323803
Интересно, однако, что set()
быстрее, когда вызывается непосредственно в range
:
timeit("""set(range(10000))""", number=1000)
# 0.3746800610097125
Это происходит быстрее, чем заданная конструкция. Вы увидите похожее поведение для других последовательностей (таких как list
s).
Моя рекомендация заключается в использовании {...}
понимания набора при создании литералов набора и в качестве альтернативы передаче понимания генератора функции set()
; и вместо этого используйте set()
для преобразования существующей последовательности/итерируемой в набор.