Почему создание класса на Python происходит намного медленнее, чем создание экземпляра класса?
Я обнаружил, что создание класса происходит медленнее, чем создание экземпляра класса.
>>> from timeit import Timer as T
>>> def calc(n):
... return T("class Haha(object): pass").timeit(n)
<<After several these 'calc' things, at least one of them have a big number, eg. 100000>>
>>> calc(9000)
15.947055101394653
>>> calc(9000)
17.39099097251892
>>> calc(9000)
18.824054956436157
>>> calc(9000)
20.33335590362549
Да, создать 9000 классов заняло 16 секунд и в последующих вызовах становится еще медленнее.
И это:
>>> T("type('Haha', b, d)", "b = (object, ); d = {}").timeit(9000)
дает похожие результаты.
Но экземпляры не страдают:
>>> T("Haha()", "class Haha(object): pass").timeit(5000000)
0.8786070346832275
5000000 экземпляров менее чем за 1 секунду.
Что делает создание дорогостоящим?
И почему процесс создания становится медленнее?
EDIT:
Как воспроизвести:
запустите новый процесс python, начальные несколько "calc (10000)" дают число 0.5 на моей машине. И попробуйте несколько более крупных значений, calc (100000), он не может закончиться даже через 10 секунд, прервать его и calc (10000), дает 15 секунд.
EDIT:
Дополнительный факт:
Если gc.collect() после "calc" становится медленным, вы можете получить "нормальную" скорость в начале, но время будет увеличиваться при последующих вызовах
>>> from a import calc
>>> calc(10000)
0.4673938751220703
>>> calc(10000)
0.4300072193145752
>>> calc(10000)
0.4270968437194824
>>> calc(10000)
0.42754602432250977
>>> calc(10000)
0.4344758987426758
>>> calc(100000)
^CTraceback (most recent call last):
File "<stdin>", line 1, in <module>
File "a.py", line 3, in calc
return T("class Haha(object): pass").timeit(n)
File "/usr/lib/python2.7/timeit.py", line 194, in timeit
timing = self.inner(it, self.timer)
File "<timeit-src>", line 6, in inner
KeyboardInterrupt
>>> import gc
>>> gc.collect()
234204
>>> calc(10000)
0.4237039089202881
>>> calc(10000)
1.5998330116271973
>>> calc(10000)
4.136359930038452
>>> calc(10000)
6.625348806381226
Ответы
Ответ 1
ахахаха! Попался!
Возможно ли это для Python без этого патча? (СОВЕТ: ЭТО БЫЛО)
Проверьте номера строк, если вы хотите проверить.
Марцин был прав: когда результаты выглядят сумасшедшими, у вас, вероятно, есть жуткий тест. Запустите gc.disable()
, и результаты воспроизвести сами. Это просто показывает, что когда вы отключите сбор мусора, вы получаете результаты мусора!
Чтобы быть более понятным, причина, по которой работает длинный тест, нарушала то, что:
-
timeit
отключает коллекции мусора, поэтому чрезмерно большие тесты занимают много (экспоненциально) дольше
-
timeit
не восстанавливал сбор мусора при исключениях
-
Вы покидаете длительный процесс с асинхронным исключением, отключая сбор мусора
Ответ 2
Это может дать вам интуицию:
>>> class Haha(object): pass
...
>>> sys.getsizeof(Haha)
904
>>> sys.getsizeof(Haha())
64
Объект класса намного более сложный и дорогой, чем экземпляр этого класса.
Ответ 3
Быстро выполните следующие функции:
def a():
class Haha(object):
pass
def b():
Haha()
дает:
2 0 LOAD_CONST 1 ('Haha')
3 LOAD_GLOBAL 0 (object)
6 BUILD_TUPLE 1
9 LOAD_CONST 2 (<code object Haha at 0x7ff3e468bab0, file "<stdin>", line 2>)
12 MAKE_FUNCTION 0
15 CALL_FUNCTION 0
18 BUILD_CLASS
19 STORE_FAST 0 (Haha)
22 LOAD_CONST 0 (None)
25 RETURN_VALUE
и
2 0 LOAD_GLOBAL 0 (Haha)
3 CALL_FUNCTION 0
6 POP_TOP
7 LOAD_CONST 0 (None)
10 RETURN_VALUE
соответственно.
По внешнему виду он просто создает больше вещей при создании класса. Он должен инициализировать класс, добавлять его в dicts и везде, а в случае Haha()
просто вызывает функцию.
Как вы заметили, делая сборку мусора, когда он получает слишком медленные скорости, снова появляется, поэтому Марцин прямо говорит, что это, возможно, проблема фрагментации памяти.
Ответ 4
Это не: только ваши надуманные тесты показывают медленное создание класса. Фактически, как показывает @Veedrac в своем ответе, этот результат является артефактом времени, отключающим сбор мусора.
Downvoters: Покажите мне непродуманный пример, когда создание классов происходит медленно.
В любом случае на ваши тайминги влияет нагрузка на вашу систему в то время. Они действительно полезны только для сравнений, выполненных в то же время. Я получаю около 0,5 с для создания 9000 классов. Фактически, это около 0,3 с на идеоне, даже когда выполняется несколько раз: http://ideone.com/Du859. Нет даже восходящего тренда.
Итак, в целом, он намного медленнее на вашем компьютере, чем другие, и на других компьютерах нет тенденции на повторные тесты (согласно вашему первоначальному требованию). Тестирование большого количества экземпляров действительно замедляет работу, по-видимому, потому, что процесс занимает много памяти. Вы показали, что выделение огромного объема памяти замедляет процесс. Молодцы.
Этот идеонный код полностью:
from timeit import Timer as T
def calc(n):
return T("class Haha(object): pass").timeit(n)
for i in xrange(30):
print calc(9000)