Сколько весит int в python
Перейти к содержимому

Сколько весит int в python

  • автор:

Python Integers

Summary: in this tutorial, you’ll learn about Python integers and how Python stores integers in the memory.

Integers are whole numbers that include negative numbers, zero, and positive numbers such as -3, -2, -1, 0, 1, 2, 3.

Python uses the class int to represent all integer numbers. All integers are objects.

How computers store integers

Computers can’t store integers directly. Instead, they only can store binary numbers such as 0 and 1.

To store integers, the computers need to use binary numbers to represent the integers.

For example, to store the number 5, the computers need to represent it using a base-2 number:

5 = 1 x 2 2 + 0 x 2 1 + 1 x 2 0

As you can see, it takes 3 bits to store the number 5 in the memory:

Suppose that you have 8 bits, you can store up to 255 integers from zero to 255:

255= 1x 2 7 + 1 x 2 6 + 1 x 2 5 + 1x 2 4 + 1 x 2 3 + 1 x 2 2 + 1x 2 1 + 1 x 2 0

By using 8 bits, you can store up to 2 8 – 1 = 255 integers.

To store both negative integers, zero, and positive integers, you need to reserve 1 bit for storing the sign, negative (-) and positive (+). Therefore, with 8 bits:

  • The largest integer that the computers can represent is 2 7 = 127.
  • And the computers can store all integers in the range (-127, 127)

Because the number zero doesn’t have a sign, the computers can squeeze out an extra number. Therefore, 8 bits can store all integers from -128 to 127.

8-bits = [-2 7 , 2 7 – 1]

Similarly, if you want to use 16 bits, 32 bits, and 64 bits to store integers, the ranges would be:

How Python represents integers

Other programming languages such as Java and C# use a fixed number of bits to store integers.

For example, C# has the int type that uses 32-bits and the long type that uses 64 bits to represent integers. Based on the integer types, you can determine the ranges of the integers those types can represent.

Python, however, doesn’t use a fixed number of bit to store integers. Instead, Python uses a variable number of bits to store integers. For example, 8 bits, 16 bits, 32 bits, 64 bits, 128 bits, and so on.

The maximum integer number that Python can represent depends on the memory available.

Also, integers are objects. Python needs an extra fixed number of bytes as an overhead for each integer.

It’s important to note that the bigger the integer numbers are, the slower the calculations such as + , — , … will be.

Python int type

The following defines a variable that references an integer and uses the type() function to get the class name of the integer:

As you can see clearly from the ouput, an integer is an instance of the class int .

Getting the size of an integer

To get the size of an integer, you use the getsizeof() function of the sys module.

The getsizeof() function returns the number of bytes that Python uses to represent an integer. For example:

To store the number 0, Python uses 24 bytes. Since storing the number zero, Python needs to use only 1 bit. Note that 1 byte equals 8 bits.

Therefore, you can think that Python uses 24 bytes as an overhead for storing an integer object.

The following returns the size of the integer 100:

It returns 28 bytes. Since 24 bytes is an overhead, Python uses 4 bytes to represent the number 100.

The following shows the size of the integer 2 64 :

So to store the integer 2 64 , Python uses 36 bytes.

Python integer operations

Python integers support all standard operations including:

  • Addition +
  • Subtraction –
  • Multiplication *
  • Division /
  • Exponents **

The result of addition, subtraction, multiplication, and exponents of integers is an integer. For example:

However, the division of two integers always returns a floating-point number. For example:

Использование памяти в Python

image

Меня часто донимали размышление о том, насколько эффективно Python использует память по сравнению с другими языками программирования. Например, сколько памяти нужно, чтобы работать с 1 миллионом целых чисел? А с тем же количеством строк произвольной длины?
Как оказалось, в Python есть возможность получить необходимую информацию прямо из интерактивной консоли, не обращаясь к исходному коду на C (хотя, для верности, мы туда все таки заглянем).
Удовлетворив любопытство, мы залезем внутрь типов данных и узнаем, на что именно расходуется память.

Все примеры были сделаны в CPython версии 2.7.4 на 32 битной машине. В конце приведена таблица для потребности в памяти на 64 битной машине.

Необходимые инструменты
sys.getsizeof и метод __sizeof__()

Первый инструмент, который нам потребуется находится в стандартной библиотеки sys. Цитируем официальную документацию:

sys.getsizeof(объект[, значение_по_умолчанию])

Возвращает размер объекта в байтах.
Если указано значение по умолчанию, то оно вернется, если объект не предоставляет способа получить размер. В противном случае возникнет исключение TypeError.
Getsizeof() вызывает метод объекта __sizeof__ и добавляет размер дополнительной информации, которая хранится для сборщика мусора, если он используется.

Алгоритм работы getsizeof(), переписанной на Python, мог бы выглядеть следующем образом:

Где PyGC_Head — элемент двойного связанного списка, который используется сборщиком мусора для обнаружения кольцевых ссылок. В исходном коде он представлен следующей структурой:

Размер PyGC_Head будет равен 12 байт на 32 битной и 24 байта на 64 битной машине.

Попробуем вызвать getsizeof() в консоли и посмотрим, что получится:

За исключением магии с проверкой флагов, все очень просто.
Как видно из примера, int и float занимают 12 и 16 байт соответственно. Str занимает 21 байт и еще по одному байту на каждый символ содержимого. Пустой кортеж занимает 12 байт, и дополнительно 4 байта на каждый элемент. Для простых типов данных (которые не содержат ссылок на другие объекты, и соответственно, не отслеживаются сборщиком мусора), значение sys.getsizeof равно значению, возвращаемого методом __sizeof__().

id() и ctypes.string_at

Теперь выясним, на что именно расходуется память.
Для этого нужно нам нужны две вещи: во-первых, узнать, где именно хранится объект, а во-вторых, получить прямой доступ на чтение из памяти. Несмотря на то, что Python тщательно оберегает нас от прямого обращения к памяти, это сделать все таки возможно. При этом нужно быть осторожным, так как это может привести к ошибке сегментирования.

Встроенная функция id() возвращает адрес памяти, где храниться начала объекта (сам объект является C структурой)

Чтобы считать данные по адресу памяти нужно воспользоваться функцией string_at из модуля ctypes. Ее официальное описание не очень подробное:

Теперь попробуем считать данные по адресу, который вернул нам id():

Вид шестнадцатеричного кода не очень впечатляет, но мы близки к истине.

Модель Struct

Для того чтобы представить вывод в значения, удобные для восприятия, воспользуемся еще одним модулем. Здесь нам поможет функция unpack() из модуля struct.

struct
Этот модуль производит преобразование между значениями Python и структурами на C, представленными в виде строк.

struct.unpack(формат, строка)
Разбирает строку в соответствие с данным форматов. Всегда возвращает кортеж, даже если строка содержит только один элемент. Строка должна содержать в точности то количество информации, как описано форматом.

Форматы данных, которые нам потребуются.

символ Значение C Значение Python Длина на 32битной машине
c char Строка из одного символа 1
i int int 4
l long int 4
L unsigned long int 4
d double float 8

Теперь собираем все вместе и посмотрим на внутреннее устройство некоторых типов данных.

О формате значений несложно догадаться.

Первое число (373) — количество указателей, на объект.

Как видно, число увеличилось на единицу, после того как мы создали еще одну ссылку на объект.

Второе число (136770080) — указатель (id) на тип объекта:

Третье число (1) — непосредственно содержимое объекта.

Наши догадки можно подтвердить, заглянув в исходный код CPython

Здесь PyObject_HEAD — макрос, общий для всех встроенных объектов, а ob_ival — значение типа long. Макрос PyObject_HEAD добавляет счетчик количества указателей на объект и указатель на родительский тип объекта — как раз то, что мы и видели.

Float

Число с плавающей запятой очень похоже на int, но представлено в памяти C значением типа double.

В этом легко убедиться:

Строка (Str)

Строка представлена в виде массива символов, оканчивающимся нулевым байтом. Также в структуре строки отдельного сохраняется ее длина, хэш от ее содержания и флаг, определяющий, хранится ли она во внутреннем кэше interned.

Макрос PyObject_VAR_HEAD включает в себя PyObject_HEAD и добавляет значение long ob_ival, в котором хранится длина строки.

Четвертое значение соответствует хэшу от строки, в чем нетрудно убедиться.

Как видно, значение sstate равно 0, так что строка сейчас не кэшируется. Попробуем ее добавить в кэш:

Кортеж (Tuple)

Кортеж представлен в виде массива из указателей. Так как его использование может приводить к возникновению кольцевых ссылок, он отслеживается сборщиком мусора, на что расходуется дополнительная память (об этом нам напоминает вызов sys.getsizeof())

Структура tuple похоже на строку, только в ней отсутствуют специальные поля, кроме длины.

Как видим из примера, последние три элементы кортежа являются указателями на его содержимое.

Остальные базовые типы данных (unicode, list, dict, set, frozenset) можно исследовать аналогичным образом.

Что в итоге?
Тип Имя в CPython формат Формат, для вложенных объектов Длина на 32bit Длина на 64bit Память для GC*
Int PyIntObject LLl 12 24
float PyFloatObject LLd 16 24
str PyStringObject LLLli+c*(длина+1) 21+длина 37+длина
unicode PyUnicodeObject LLLLlL L*(длина+1) 28+4*длина 52+4*длина
tuple PyTupleObject LLL+L*длина 12+4*длина 24+8*длина Есть
list PyListObject L*5 L*длину 20+4*длина 40+8*длина Есть
Set/
frozenset
PySetObject L*7+(lL)*8+lL LL* длина (<=5 элементов) 100
(>5 элементов) 100+8*длина
(<=5 элементов) 200
(>5 элементов) 200+16*длина
Есть
dict PyDictObject L*7+(lLL)*8 lLL*длина (<=5 элементов) 124
(>5 элементов) 124+12*длина
(<=5 элементов) 248
(>5 элементов) 248+24*длина
Есть

* Добавляет 12 байт на 32 битной машине и 32 байта на 64 битной машине

Мы видим, что простые типы данных в Python в два-три раза больше своих прототипов на C. Разница обусловлена необходимостью хранить количество ссылок на объект и указатель на его тип (содержимое макроса PyObject_HEAD). Частично это компенсируется внутренним кэшированием, который позволяет повторно использовать ранее созданные объекты (это возможно только для неизменяемых типов).

Для строк и кортежей разница не такая значительная — добавляется некоторая постоянная величина.

А списки, словари и множества, как правило, занимают больше на 1/3, чем необходимо. Это обусловлено реализацией алгоритма добавления новых элементов, который приносит в жертву память ради экономии времени процессора.

Итак, отвечаем на вопрос в начале статьи: чтобы сохранить 1 миллион целых чисел нам потребуется 11.4 мегабайт (12*10^6 байт) на сами числа и дополнительно 3.8 мегабайт (12 + 4 + 4*10^6 байт) на кортеж, которых будет хранить на них ссылки.

UPD: Опечатки.
UPD: В подзаголовке «1 миллион целых чисел», вместо «1 миллион простых чисел»

Сколько весит int в python

These represent numbers in the range -2147483648 through 2147483647. (The range may be larger on machines with a larger natural word size, but not smaller.) When the result of an operation would fall outside this range, the result is normally returned as a long integer (in some cases, the exception OverflowError is raised instead). For the purpose of shift and mask operations, integers are assumed to have a binary, 2’s complement notation using 32 or more bits, and hiding no bits from the user (i.e., all 4294967296 different bit patterns correspond to different values).

Plain integers (also just called integers) are implemented using long in C, which gives them at least 32 bits of precision (sys.maxint is always set to the maximum plain integer value for the current platform; the minimum value is -sys.maxint — 1). Long integers have unlimited precision.

Numbers are created by numeric literals or as the result of built-in functions and operators. Unadorned integer literals (including binary, hex, and octal numbers) yield plain integers unless the value they denote is too large to be represented as a plain integer, in which case they yield a long integer. Integer literals with an ‘L’ or ‘l’ suffix yield long integers (‘L’ is preferred because 1l looks too much like eleven!).

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *