Пользователь
0,0
рейтинг
10 декабря 2014 в 13:09

Разработка → Память и числа в Python из песочницы

Hi, All.

Ранее я работал с C-подобными языками, теперь же пришлось сесть за Python. Синтаксис дался легко, и наступил черёд каверзных вопросов. Под катом — статья про то как в Python реализовано хранение данных в памяти. Не претендую на истину, но пробую разобраться.

Смотрим на ссылки


Начнём с самого простого. Любые данные в Python это объект, любая переменная — ссылка на объект. Не существует никаких данных, которые не являются объектами. Для начала нам нужно научиться определять являются ли два «одинаковых» объекта одним и тем же. Для этого требуется получить адрес, что легко позволяет сделать встроенная функция id(). Пробуем:

print(id(0))

Как и ожидалось, выводится что-то невразумительное. Большое число, вероятно действительно адрес. Но если каждое число, использующееся на протяжении хранить в памяти, то никакой памяти, естественно, не хватит. Проводится короткий эксперимент:

print(id(0))
print(id(0))

Два абсолютно одинаковых числа. Следовательно, все константные числа на самом деле хранятся в памяти не дублираясь. Логично — у Python и так низкая производительность, такое ухищрение позволяет сохранить последние её остатки. Окей, попробуем заполнить всю память огромным массивом нулей.

a = [0]
while True:
    a += [0]

Бесконечный цикл, как и положено, выполняется бесконечно, но памяти практически не требует. Ещё один эксперимент:

a = [0, 0]
print(id(a[0]))
print(id(a[1]))

Ну да, одно и то же число. Скорее чтобы подтвердить провожу такую же проверку с двумя разными переменными — одно и то же число, да ещё и равное id(0). То есть, алгоритм, видимо такой: когда у нас меняется значение переменной мы проверяем нет ли в памяти такого же, и, если есть, перенаправляем ссылку на него. Это поведение требуется, очевидно, потому что объект занимает в памяти довольно много места, и, чтобы быть более компактным, Python по максимуму использует существующие объекты. Чтобы не загромождать статью кодом скажу что для строк (в том числе, полученных через срез), логических объектов и даже массивов это работает так же. Сделаем вторую попытку занять Python'ом всю память:

i = 0
a = [0]
while True:
    a += [a[i]]
    i += 1

Успех! Потребление памяти постоянно повышается. Делаем первый вывод:
1. Любые данные в Python это объекты.
2. Если объекты «одинаковы», то они хранятся по одному адресу в памяти. Иными словами, a == b и id(a) == id(b) это эквивалентные утверждения.
3. Более сложной оптимизации не используется — довольно простая зависимость в массиве уже не оптимизируется никак (только правило «a[i] = i»). Впрочем, я бы удивился, если бы использовалась: тут уже требуется довольно сложный лексический анализ, который Python с его пошаговой интерпретацией позволить себе не может.

Считаем ссылки


Disclaimer: сейчас мы будем работать в интерактивном режиме Python'а. Для того, чтобы подсчитать ссылки на объект есть функция sys.getrefcount(). Импортируем sys:

>>> from os import sys

И для начала нам нужно определить насколько реальными являются данные, которые она выдаёт:

>>> sys.getrefcount('There is no this string in Python')
3
>>> sys.getrefcount('9695c3716e3b801367b7eca6a3281ac9') #md5-хеш 512 рандомных байт из /dev/urandom.
3
>>> a = 'More random for the random god!'
>>> sys.getrefcount(a)
2
>>> a = 0
>>> sys.getrefcount(a)
434
>>> sys.getrefcount(0)
436

Это говорит нам об одной забавной вещи: считая ссылки, getrefcount() сам их создаёт. Как мы видим, для констант он создаёт их две штуки (действительно две, я пробовал на больших объёмах входных данных, которые здесь не публикую за ненадобностью), так что мы можем просто вычитать 2. На самом деле, видимо для переменных он тоже создаёт две, но не учитывает при этом саму переменную. Ну ладно, отклонениями результатов от реальности мы разобрались. Теперь несколько примеров:

>>> sys.getrefcount(1)
754
>>> sys.getrefcount(65)
13
>>> sys.getrefcount(67)
11
>>> sys.getrefcount('A')
4
>>> sys.getrefcount('a')
6
>>> sys.getrefcount(False)
100
>>> sys.getrefcount(True)
101
# Истина победила!

Почему тут внезапно появляются указатели на ровном месте (целыя 751 штука для единицы)? Потому что эта функция считает C-шные указатели, то есть включает те, которые используются в коде самого Python. Фактически, мы нагло вламываемся в ту часть Python, которую от нас стремятся скрыть разработчики.

Ну, вот такая вот закулиса у Python. Если дойдут руки и сумею — напишу о том, что произойдёт, если попробовать поменять эти объекты вручную через OllyDbg, скажем.
@DividedByZero
карма
–1,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (12)

  • +13
    У Вас написано довольно много ереси и глупостей:
    1. Любые данные в Python это об<ъекты.
    2. Если объекты «одинаковы», то они хранятся по одному адресу в памяти. Иными словами, a == b и id(a) == id(b) это эквивалентные утверждения.

    >>> a = [1, 2, 3]
    >>> b = [1, 2, 3]
    >>> a == b
    True
    >>> id(a) == id(b)
    False

    Касательно именно чисел Вам надо почитать, что такое free list.
  • +7
    Статья — бред.
    Если тема действительно интересна, начать копать нужно, как минимум, с изучения исходников питона, но если это слишком сложно, то можно просто погуглить. Вот первая достаточно интересная ссылка по теме: stackoverflow.com/questions/132988/is-there-a-difference-between-and-is-in-python
    • –3
      А почему надо смотреть исходники? У каждого промышленного ЯП есть стандарт где все описывается а уж компилятор или компиляторы его реализую. Так что надо не исходникам лазить, а доки читать.
      • +1
        Под катом — статья про то как в Python реализовано хранение данных в памяти

        И простыня полного бреда со «срывом покровов» и «сенсационными заявлениями» вида:
        Более сложной оптимизации не используется

        Причём все это «методом тыка», умозрительные (и неверные!) заключения вообще ни о чём.

        В питоне со стандартами не всё так хорошо. К тому же наш автор пишет (сам того не осознавая) о CPython, а не о питоне. А CPython, как любой компилятор/интерпретатор использует кучу хаков (например, кэширование integers) и это нормально, только вот я сомневаюсь, что в стандартах языка такие вещи вообще целесообразно описывать. Один компилятор/интерпретатор делает так, другой — иначе.
  • +3
    А вот маленькая статья о кешировании чисел в питоне
    • 0
      удалено
  • +1
    Автор, сделайте
    print(id(257)) 
    print(id(257))
    
  • 0
    Иными словами, a == b и id(a) == id(b) это эквивалентные утверждения.

    Из id(a) == id(b) следует a == b, обратное не верно
    • 0
      Нет. Вы можете переопределить __eq__ любым угодным вам способом. Другой вопрос, что в ряде мест (к примеру, при сравнении списков) применяется сравнение id() перед сравнением объектов: есть известный пример с NaN, или вот ещё:

      Python 2.7.8 (a980ebb26592ed26706cd33a4e05eb45b5d3ea09, Nov 28 2014, 06:20:07)
      [PyPy 2.4.0] on linux2
      Type "help", "copyright", "credits" or "license" for more information.
      >>>> class NE(object):
      ....    def __eq__(self, other):
      ....            return False
      ....
      >>>> n = NE()
      >>>> [1, 2, n] == [1, 2, n]
      True
      >>>> n == n
      False


      . В CPython и jython картина точно такая же. IronPython покажет два False, но у него всегда были проблемы с совместимостью.
  • 0
    возвращайтесь к Си
  • 0
    Вот еще интересный вопрос в тему с SO

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