Python: как уменьшить расход памяти вдвое, добавив всего одну строчку кода?

    Привет habr.

    В одном проекте, где необходимо было хранить и обрабатывать довольно большой динамический список, тестировщики стали жаловаться на нехватку памяти. Простой способ, как «малой кровью» исправить проблему, добавив лишь одну строку кода, описан ниже. Результат на картинке:


    Как это работает, продолжение под катом.

    Рассмотрим простой «учебный» пример — создадим класс DataItem, содержащий персональные данные о человеке, например имя, возраст и адрес.
    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))

    Получаем ответ 56 байт. Вроде немного, вполне устраивает.
    Однако, проверяем на другом объекте, в котором данных больше:
    d2 = DataItem("Boris", 24, "In the middle of nowhere")
    print ("sys.getsizeof(d2):", sys.getsizeof(d2))

    Ответ — снова 56. На этом моменте понимаем, что что-то здесь не то, и не все так просто, как кажется на первый взгляд.

    Интуиция нас не подводит, и все действительно не так просто. Python — это очень гибкий язык с динамической типизацией, и для своей работы он хранит туеву хучу немалое количество дополнительных данных. Которые и сами по себе занимают немало. Просто для примера, sys.getsizeof("") вернет 33 — да, целых 33 байта на пустую строку! А sys.getsizeof(1) вернет 24 — 24 байта для целого числа (программистов на Си прошу отойти от экрана и дальше не читать, дабы не утратить веру в прекрасное). Для более сложных элементов, таких как словарь, sys.getsizeof(dict()) вернет 272 байта — и это для пустого словаря. Дальше продолжать не буду, принцип надеюсь ясен, да и производителям RAM нужно же продавать свои чипы.

    Однако вернемся к нашему классу DataItem и «детскому» вопросу. Сколько занимает такой класс в памяти? Для начала, выведем целиком все содержимое класса на более низком уровне:
    def dump(obj):
      for attr in dir(obj):
        print("  obj.%s = %r" % (attr, getattr(obj, attr)))

    Эта функция покажет то, что скрыто «под капотом», чтобы все функции Python (типизация, наследование и прочие плюшки) могли функционировать.
    Результат впечатляет:


    Сколько это все занимает целиком? На github нашлась функция, подсчитывающая реальный объем данных, рекурсивно вызывая getsizeof для всех объектов.
    def get_size(obj, seen=None):
        # From https://goshippo.com/blog/measure-real-size-any-python-object/
        # Recursively finds size of objects
        size = sys.getsizeof(obj)
        if seen is None:
            seen = set()
        obj_id = id(obj)
        if obj_id in seen:
            return 0
    
        # Important mark as seen *before* entering recursion to gracefully handle
        # self-referential objects
        seen.add(obj_id)
        if isinstance(obj, dict):
          size += sum([get_size(v, seen) for v in obj.values()])
          size += sum([get_size(k, seen) for k in obj.keys()])
        elif hasattr(obj, '__dict__'):
          size += get_size(obj.__dict__, seen)
        elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes, bytearray)):
          size += sum([get_size(i, seen) for i in obj])
        return size
    

    Пробуем ее:
    d1 = DataItem("Alex", 42, "-")
    print ("get_size(d1):", get_size(d1))
    
    d2 = DataItem("Boris", 24, "In the middle of nowhere")
    print ("get_size(d2):", get_size(d2))

    Получаем 460 и 484 байта соответственно, что больше похоже на правду.

    Имея эту функцию, можно провести ряд экспериментов. Например интересно, сколько места займут данные, если структуры DataItem положить в список. Функция get_size([d1]) возвращает 532 байта — видимо, это «те самые» 460 + некоторые накладные расходы. А вот get_size([d1, d2]) вернет 863 байта — меньше, чем 460 + 484 по отдельности. Еще интереснее результат для get_size([d1, d2, d1]) — мы получаем 871 байт, лишь чуть больше, т.е. Python достаточно «умен» чтобы не выделять память под один и тот же объект второй раз.

    Теперь мы переходим ко второй части вопроса — можно ли уменьшить расход памяти? Да, можно. Python это интерпретатор, и мы в любой момент можем расширить наш класс, например добавить новое поле:
    d1 = DataItem("Alex", 42, "-")
    print ("get_size(d1):", get_size(d1))
    
    d1.weight = 66
    print ("get_size(d1):", get_size(d1))

    Это замечательно, но если нам не нужна эта функциональность, мы можем принудительно указать интерпретатору список объектов класса с помощью директивы __slots__:
    class DataItem(object):
        __slots__ = ['name', 'age', 'address']
        def __init__(self, name, age, address):
            self.name = name
            self.age = age
            self.address = address
    

    Более подробно прочитать можно в документации (RTFM), в которой написано что "__slots__ allow us to explicitly declare data members (like properties) and deny the creation of __dict__ and __weakref__. The space saved over using __dict__ can be significant".
    Проверяем: да, действительно significant, get_size(d1) возвращает… 64 байта вместо 460, т.е. в 7 раз меньше. Как бонус, создаются объекты примерно на 20% быстре (см. первый скриншот статьи).

    Увы, при реальном использовании такого большого выигрыша в памяти не будет за счет других накладных расходов. Создадим массив на 100000 простым добавлением элементов, и посмотрим расход памяти:
    data = []
    for p in range(100000):
        data.append(DataItem("Alex", 42, "middle of nowhere"))
    
    snapshot = tracemalloc.take_snapshot()
    top_stats = snapshot.statistics('lineno')
    total = sum(stat.size for stat in top_stats)
    print("Total allocated size: %.1f MB" % (total / (1024*1024)))
    

    Имеем 16.8 Мбайт без __slots__ и 6.9 Мб с ним. Не в 7 раз конечно, но и так вполне неплохо, учитывая что изменение кода было минимальным.

    Теперь о недостатках. Активация __slots__ запрещает создание всех элементов, включая и __dict__, значит к примеру, не будет работать такой код перевода структуры в json:
        def toJSON(self):
            return json.dumps(self.__dict__)
    

    Но это просто исправить, достаточно сгенерировать свой dict программно, перебрав все элементы в цикле:
        def toJSON(self):
            data = dict()
            for var in self.__slots__:
                data[var] = getattr(self, var)
            return json.dumps(data)
    


    Также невозможно будет динамически добавлять новые переменные в класс, но в моем случае этого и не требовалось.

    И последний тест на сегодня. Интересно посмотреть, сколько памяти занимает программа целиком. Добавим в конец программы бесконечный цикл, чтобы она не закрывалась, и посмотрим расход памяти в диспетчере задач Windows.
    Без __slots__:

    16.8Мб каким-то чудом превратилось (правка — объяснение чуда ниже) в 70Мб (программисты Си надеюсь еще не вернулись к экрану?).

    С включенным __slots__:


    6.9Мб превратились в 27Мб… ну, все-таки память мы сэкономили, 27Мб вместо 70 это не так уж плохо для результата добавления одной строчки кода.

    Правка: в комментариях (спасибо robert_ayrapetyan за проделанный тест) подсказали, что много дополнительной памяти занимает использование отладочной библиотеки tracemalloc. Видимо, она добавляет дополнительные элементы к каждому создаваемому объекту. Если отключить ее, суммарный расход памяти будет гораздо меньше, на скриншоте показаны 2 варианта:


    Что делать, если нужно сэкономить еще больше памяти? Это возможно с использованием библиотеки numpy, позволяющей создавать структуры в Си-стиле, но в моем случае это потребовало бы более глубокой доработки кода, да и первого способа оказалось вполне достаточно.

    Странно, что использование __slots__ ни разу не разбиралось подробно на Хабре, надеюсь, эта статья немного восполнит данный пробел.

    Вместо заключения.
    Может показаться, что данная статья является антирекламой Python, но это совсем не так. Python — очень надежный (чтобы «уронить» программу на Python надо очень сильно постараться), легко читабельный и удобный для написания кода язык. Эти плюсы во многих случаях перевешивают минусы, ну а если нужна максимальная производительность и эффективность, можно использовать библиотеки вроде numpy, написанные на С++, которые работают с данными вполне быстро и эффективно.

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

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

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

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

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

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

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

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

                            Перепишите на Nim xD

                              0
                              Была мысль на Go переписать :)
                                0

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

                          0

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

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

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


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

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

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

                    Более точные измерения есть чуть выше внутри, где я использовал tracemalloc.take_snapshot, для них скриншот не приведен, просто цифры даны в тексте.
                      +1
                      Хм, вот что удалось намерять мне. Во-первых, 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 раз».

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

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

                  0
                  Да, согласен, спасибо.
                  +2
                  >>> 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
                  


                  Но, да, как пример обход атрибутов — наверное, для понимания полезнее.
                    +6
                    Первую четверть статьи молился что бы было что-то интересное, а не slots. И неверно называть это решением на одну строку кода. В проекте может быть миллион классов, могут быть классы где аттрибуты динамические, да основные затраты вообще могут идти на типы которые нельзя отредактировать толком.

                    P.S.
                    использование __slots__ ни разу не разбиралось подробно на Хабре
                    Может я чего-то не понимаю но это все что угодно кроме подробного разбора, скорее что-то типо не большого ревью
                      –4
                      Судя по названию про «одну строчку кода», можно было догадаться про slots, да ;)

                      Естественно, в реальном проекте экономия будет меньше, ну думаю это и так очевидно.
                      0
                      Просто для примера, 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
                        0
                        Windows 10 x64, Python 2.7.14 и 3.6.2.
                          0
                          Забыл добавить, версия Python 64-битная, наверно в этом разница.
                          0

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


                          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
                          +1
                          iOS/Linux developer

                          Но, почему PowerShell и 10?
                            0
                            Ставить Linux дома я все же не готов :)

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

                                  0

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

                                  0
                                  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
                                    0
                                    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:).
                                    0
                                    "(программистов на Си прошу отойти от экрана и дальше не читать, дабы не утратить веру в прекрасное)"
                                    — поздно, инфаркт — валидол — скорая.
                                    Садист!!!
                                      0
                                      Поддерживаю. Я конечно понимаю, что с питоном все плохо… Но настолько! Пробовал пользоваться. Наткнулся на потрясающе низкую скорость выполнения. Теперь и это. Ребята, прекрасно, конечно, что вам удобно вести разработку на таком языке, где думать особо не надо. Только вот пользователю этими программами пользоваться. А потом думаешь, а что это элементарная программа так тормозит и память жрет? Знаете программистский апопкалипсис? Это когда всех питонщиков посадят на C.
                                        0
                                        На самом деле, не все так плохо.

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

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

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

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


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

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

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

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

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

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

                                            Python хорош для прототипирования. Для Computer Vision или Machine Learning хорошо пробовать много разных вещей и Python тут раскрывается во всей красе. Но если надо запилить свой математический алгоритм, то это неподходящий инструмент.
                                              +1
                                              Находится в хабе «высокая производительность». Нужно в «обычная произвоидительность» или «не низкая».

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

                                              Самое читаемое