Подтвердить что ты не робот

Почему создание класса на 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
4b9b3361

Ответ 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)