Pull to refresh

Comments 58

Вы забыли в конце резюмировать «ваш К.О.».
Когда речь заходит про «питон и оптимизацию», первым делом приводят пример _slots_ и вторым пунктом — namedtuple (попробуйте и ещё одну статью ни о чём напишите).
Самому попробовать всегда интереснее, чем где-то прочитать, так что ничего плохого не вижу.
Не обязательно сообщать всему миру, что кто-то научился ходить, а кто-то уже в детский садик пошёл.
Сайт читают не только профи, и не все знают про slots и dict. Нет ничего плохого если кто-то научится чему-то новому из этой (или любой другой) статьи.
Действительно — то, что написано в документации ярко, просто, прямо и понятно читать не надо. Так же не надо читать то, что написано вообще в любой книге. Навсидку, возьмём Лутца, Бизли, Саммерфила — у каждого это прописано чётко, доступно и понятно.

С другой стороны, выкладывали же мануалы по установке модулей для вордпрессе, сайт же читают не только профи, всё правильно.

«Power on IBM/PC for dummies».
Да, вы не поверите, но бывают и статьи для начинающих. Ваш К.О. ;)
Кстати была мысль в некоторых местах использовать dict вместо класса, но уж больно страдает читабельность кода имхо.
Cheatability здесь не при чём, здесь плохой дизайн; надо переделывать.

Я думаю в коде того кто не знает про slots и dict бутылочным горлышком будет далеко не slots, а более банальные вещи.
Хотя как на потребление памяти влияет slots в реальном коде, решаящем реальную задачу было бы интересно почитать. Только если этот реальный код не подогнан специально под slots, типа огромного количества объектов одного класа.

В моем случае ничего сверх-интересного не было, но slots все же пригодился. Надо было хранить информацию о кадрах с IP-камер (timestamp, размер, и пр), которые приходили 2 раза в секунду. Массив динамический, кадры то приходят новые, то удаляются, так что numpy array тут был бы неудобен. Все это еще и на девайсе типа Raspberry Pi, так что памяти не сильно много.

В итоге, после нескольких дней работы девайса без остановки, память просто выжралась вся, хотя объектов было не так уж много.
после нескольких дней работы девайса без остановки, память просто выжралась вся

Очевидно, что причина не в слотсах. И решать нужно проблему, а не лечить симптомы, когда уже всё сдохло.
Массив кадров нужно было хранить в памяти, т.к. он нужен для их обработки, выборка потом делается по timestamps. Можно гипотетически на диск скидывать, но тогда SD-карта (которая вместо диска) быстро помрет.

Как вариант, использовать массивы numpy, тоже думал об этом, но пока не так критично чтобы это переписывать.
Вы вместо пересказа любого учебника по питонам в посте — лучше бы задачу озвучили, это куда как интереснее и продуктивнее. :)
Задача в целом простая, я чуть ниже ответил, хранение изображений с IP-камер в «облаке», для этого есть автономный девайс типа Raspberry Pi который все это хранит и обрабатывает. Т.к. девайс простой, есть ограничения на количество циклов записи на карту, ну и на память в целом (PS: переписать все на Си не предлагать;).

Ну не, тогда уж лучше на Си )))

Я думаю YaakovTooth, и не только, хочет сказать что slots может только отсрочить проблему на какое-то время, а потом добавится ктоме timestamp еще несколько полей и проблему все равно прийдется решать более основательно.

Да, согласен разумеется, бесконечной памяти никто и нигде не обещал.

Конечно никто тут кроме вас не знает вашу задачу и ее ограничения.


Но так на вскидку: неужели все эти кадры надо в памяти хранить, а не прочитал > обработал > при необходимости записал куда-то > выкинул из памяти > побежал дальше.

Там в общем, так и было — информация о кадрах хранится в памяти, затем если интернет есть, все это уходит на сервер в Azure, кадр удаляется. Но если по какой-то причине интернета нет, массив начинает заполняться и расти.

Ситуацию «интернета нет месяц» я все же не рассматриваю как нереальную, так что данной оптимизации в принципе было достаточно.
> 16.8Мб каким-то чудом превратилось в 70Мб
Так это память интерпретатора, не имеет отношения к вашему коду.
Вероятно да, но как видно из результатов, включение/выключение slots на память интерпретатора ощутимо влияет тоже.
В статье этого как раз и не видно, мб пропустил. Вы меряете свои структуры и выводите размеры в коде, а потом сразу бац — скриншот из менеджера задач. Покажите какой был размер, занимаемый процессом до\после. А потом еще и «пустым» запущенным процессом интерпритатора, чтобы знать его собственный размер.
Там 2 скриншота из менеджера задач, которые отличаются только slots. Полагаю что его наличие или отсутствие сильно влияет на внутреннюю структуру интерпретатора, как именно хз, не вникал.

Более точные измерения есть чуть выше внутри, где я использовал tracemalloc.take_snapshot, для них скриншот не приведен, просто цифры даны в тексте.
Хм, вот что удалось намерять мне. Во-первых, tracemalloc генерит большой оверхед (это к чуду превращения 17Мб в 70Мб), надо от него избавляться.

1. Выделения памяти пустым скриптом (фактически это память, необходимая самому интерпретатору, чтобы просто запуститься):
dtrace -n 'syscall::mmap:entry { @ = sum(arg1); }' -c "python empty.py"

35950592 (34MB)

2. Создаем скрипт по мотивам вашей логики, без доп. оверхеда:

test.py
class DataItem(object):
    # __slots__ = ['name', 'age', 'address']
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address


data = []


def foo():
    for p in range(100000):
        data.append(DataItem("Alex", 42, "middle of nowhere"))


foo()


2.1. Без слотов:

dtrace -n 'syscall::mmap:entry { @ = sum(arg1); }' -c "python test.py"

53514240 (51MB)

2.2. Со слотами:

dtrace -n 'syscall::mmap:entry { @ = sum(arg1); }' -c "python test.py"

42766336 (40MB)

Если вычесть память самого питоновского движка (34MB), получим соотв. 17 и 6 МБ под сами структуры (почти то же самое, что намерили вы).

Интересно, что зависимость не линейная (это уже паттерны выделения памяти ОС): увеличив повторения цикла в 10 раз (1 млн. повторений), получим
182Мб и 291Мб соответственно, еще в 10 раз (10 млн. повторений) получим 1494Мб и 2835Мб, т.е. заголовок статьи можно поменять на «от 1.5 до 2.5 раз».

Спасибо за полезное дополнение, добавил в текст.

На всякий случай, большая часть атрибутов из первой картинки — это атрибуты класса. Добавьте в функцию dump вывод id(attr), вызовите ее второй раз с аргументом DataItem и увидите, что почти все адреса совпадают. Соответственно и функцию подсчета нужно модифицировать, чтобы она эти общие для всех инстансов атрибуты не учитывала.

>>> from pympler import asizeof
>>> asizeof.asized(d1, detail=1).format()
<DataItem object at 0x7fde39dc8ba8> size=480 flat=56
    __dict__ size=424 flat=112
    __class__ size=0 flat=0


Но, да, как пример обход атрибутов — наверное, для понимания полезнее.
UFO just landed and posted this here
Судя по названию про «одну строчку кода», можно было догадаться про slots, да ;)

Естественно, в реальном проекте экономия будет меньше, ну думаю это и так очевидно.
Просто для примера, sys.getsizeof("") вернет 33 — да, целых 33 байта на пустую строку! А sys.getsizeof(1) вернет 24 — 24 байта для целого числа

Это какая ОС/версия питон?

Win 7 x64, Python 2.7.15

sys.getsizeof("") # 21
sys.getsizeof(целое_число) # от 12 и выше
sys.getsizeof(dict())  # 140

Win 7 x64, Python 3.6.6

sys.getsizeof("") # 25
sys.getsizeof(целое_число) # от 14 и выше
sys.getsizeof(dict())   # 136
Забыл добавить, версия Python 64-битная, наверно в этом разница.

Да ладно у автора еще нормально, бывает и хуже


import sys
v = sys.version.replace("\n", "| ")
print(f'version: {v}')
print(f'string: {sys.getsizeof("")}')
print(f'number: {sys.getsizeof(11)}')
print(f'dict:   {sys.getsizeof({})}')

Out


version: 3.6.5 (default, Apr  1 2018, 05:46:30) | [GCC 7.3.0]
string: 49
number: 28
dict:   240
Ставить Linux дома я все же не готов :)

Ну и Python вполне кроссплатформенная штука, вышеприведенный код работает везде, от Винды до Raspberry Pi или OSX.
Интересно было бы сравнить еще с data class которыйэ добавили в
3.7. Может это синтаксический сахар. А может и нет
Python. К вершинам мастерства. стр. 293. Там много чего интересного написано, про что не было статей на хабре.
не программист, просто для интереса делаю так:
import sys

class DataItem(object):
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address
d1 = DataItem("Alex", 42, "-")
print ("sys.getsizeof(d1):", sys.getsizeof(d1))

class DataItem1(object):
    __slots__ = ['name', 'age', 'address']
    def __init__(self, name, age, address):
        self.name = name
        self.age = age
        self.address = address
d2 = DataItem1("Alex", 42, "-")
print ("sys.getsizeof(d2):", sys.getsizeof(d2))


('sys.getsizeof(d1):', 64)
('sys.getsizeof(d2):', 72)

ЧЯДНТ?

P.S.
python -V Python 2.7.15rc1 Ubuntu 18.04.1 LTS

Используйте скрипт из поста, sys.getsizeof для сложных типов не все

It is also not necessary to read what is written in general in any book. Along the hook, take Lutz, Beasley, Summerfil — each one is spelled out clearly, accessiblely and clearly.
Although how the memory consumption is affected by the slots in the real code, it would be interesting to read the real problem solver. Only if this real code is not tailored specifically for slots, such as a huge number of objects of the same class. Richestsoft
This was already discussed in the first comments thread. Many people are using Python for fun or for their hobby projects or as a helper tool for other projects, and they are not aware of using slots or dict. This article is mostly for beginners.

I also described briefly my real task in the first thread as well (you can use google translate to read it:).
"(программистов на Си прошу отойти от экрана и дальше не читать, дабы не утратить веру в прекрасное)"
— поздно, инфаркт — валидол — скорая.
Садист!!!
Поддерживаю. Я конечно понимаю, что с питоном все плохо… Но настолько! Пробовал пользоваться. Наткнулся на потрясающе низкую скорость выполнения. Теперь и это. Ребята, прекрасно, конечно, что вам удобно вести разработку на таком языке, где думать особо не надо. Только вот пользователю этими программами пользоваться. А потом думаешь, а что это элементарная программа так тормозит и память жрет? Знаете программистский апопкалипсис? Это когда всех питонщиков посадят на C.
На самом деле, не все так плохо.

Питон это интерпретатор, и естественно, он работает медленнее. Но его библиотеки написаны как раз на Си, и при их грамотном использовании, код лишь чуть уступает по скорости. Еще к питону можно и свои С-библиотеки подключать, да много чего там есть.

На самом деле, Python обманчиво простой язык, с низким порогом входа, но писать на нем эффективные программы далеко не так просто, и требует понимания не меньшего, чем для С-программистов.
Вы же наверное понимаете почему в питоне целое число занимает 24 байта и более? Вот простейший для питона код, который объясняет это (** — это возведение в степень):

print(10**5000 + 23**1200 — 67**150)

Для питона такие большие ЦЕЛЫЕ числа — не проблема. Поэтому они занимают столько много места.
Интересно сколько времени займёт написать на C аналогичный пример?
Для начала, расскажите, в каких областях техники оперируют такими степенями. В жизни вы НИКОГДА с такими числами не столкнетесь. И считанные программисты хоть раз в жизни с этим сталкиваются. Это такой сферический конь в вакууме. Для остальных с головой достаточно типа Double. Если совсем невмоготу, то есть Extended, который поддерживает указанный вами диапазон от 3.37 x 10**-4932 до 1.18 x 10**4932 и занимает 16 байт в памяти. Поэтому питон никакого сверхестественного диапазона чисел не обеспечивает. Все его библиотеки написаны на C. Но озвучу здесь маленькую догадку, откуда взялся размер 24 байта. Наиболее вероятно, что переменные хранятся в банальном типе Variant. Это наиболее подходящий способ хранения переменных для такого языка.
Для начала, расскажите, в каких областях техники оперируют такими степенями. В жизни вы НИКОГДА с такими числами не столкнетесь.


При работе с криптовалютами.
В одном биткойне 10⁸ сатоши. Всего же в системе общее количество сатоши, если я ничего не напутал — примерно 2.1·10¹⁵ сатоши.
В одном эфире 10¹⁸ wei, а для учёта суммарного total supply понадобится работать с числами порядка 10²⁶. Но при этом в системе используются 256-битные целые, поэтому надо уметь работать с числами порядка 10⁷⁸.
В той же Universa в создаваемых смарт-контрактах (а значит, и токенах) «дробность валюты» вообще не ограничена, даже конкретным размером целых чисел.

И да, не путайте целые числа с double. Все те варианты, которые вы предложили (double/extended), для финансовых операций не подходят в принципе.
Когда криптография начиналась, ключи 256 битные появились, о питоне никто и не слышал. И как-то обходились, причем на значительно менее мощных компьютерах. То, что от вас спрятали библиотеку, написанную на C, не означает, что это заслуга питона. Это его недостаток.
Это не 256-битные ключи, это 256-битные целые числа — при чём тут криптография?

И опять же, вы куда-то пошли не в ту степь. Я вам показал наглядный пример, нарушающий ваше предположение «в жизни вы НИКОГДА с такими числами не столкнетесь» — а вы поддержку, по сути, BigInteger-ов (в терминологии Java) называете недостатком. Что ещё тогда «недостаток»? Удобные высокоуровневые фронтенды над epoll? «Спрятанные библиотеки» работы с HTTP на высоком уровне? Нужно больше таких «недостатков», пожалуй.

но питон написан на си… Т е его писали программисты на си… Сразу возникает вопрос — как они не померли и зачем сделали это? Это были садомазохисты видимо...

Засирателям кармы и минусаторам посвящается, вот так материал на эту тему выглядит с претензией на полноту:
Немного смешно читать про оптимизацию Python программ средствами самого Python. 1000 объектов теперь занимают не 100МБ, а 50МБ. Вау! При том, что на C/C++ эти же 1000 объектов уместяться в 64к.

А вообще, писать ускоряющие С/С++/Cython библиотеки для Python — это мой хлеб. И первое, что дает прирост в десятки, а иногда и сотни раз — отказ от использования питоновских объектов и питоновского же менеджера памяти. Больше всего мне нравится, когда в конце разработки клиент замеряет скорость работы и у него округляются глаза. Вычисления зависели, скажем, как O(n) и клиент начинал уже придумывать, как ему уменьшить «n», чтобы вложиться во временной интервал. А после ускорения оказывалось, что даже самый большой «n» клиента вписывается с запасом в одну миллисекунду.

Python хорош для прототипирования. Для Computer Vision или Machine Learning хорошо пробовать много разных вещей и Python тут раскрывается во всей красе. Но если надо запилить свой математический алгоритм, то это неподходящий инструмент.
Находится в хабе «высокая производительность». Нужно в «обычная произвоидительность» или «не низкая».
Most likely, what you’re thinking of is some kind of time/space trade-off that makes Python programs consume less memory at the price of running much more slowly…or some other horrible down-side.
If it worked without compromises — then the Python interpreter would enable whatever it does by default…and it doesn’t. So we may deduce that this is not (on balance) a good idea for the majority of programs. HindiNews
Sign up to leave a comment.

Articles