Обзор
Если данные, обрабатываемые программой, относительно большие и сложные, они будут занимать большой объем памяти во время работы программы.Когда занятость памяти достигает определенного значения, программа может быть завершена операционной системой, особенно при ограничении память, используемая программой.Большие сцены более подвержены проблемам. Ниже я приведу несколько способов оптимизации использования памяти Python.
Объяснение: Следующий код выполняется в Python3.
взять каштан
Давайте возьмем простую сцену и используем Python для хранения данных трехмерных координат x, y, z.
Dict
Использовать встроенную структуру данных Python, Dict, для реализации требований приведенного выше примера очень просто.
>>> ob = {'x':1, 'y':2, 'z':3}
>>> x = ob['x']
>>> ob['y'] = y
Просмотрите объем памяти, занимаемый следующим объектом ob:
>>> print(sys.getsizeof(ob))
240
Простые три целых числа занимают много памяти Представьте себе следующее: если таких данных нужно хранить большое количество, они будут занимать больше памяти.
| Объем данных | Занятый объем памяти |
|---|---|
| 1 000 000 | 240 Mb |
| 10 000 000 | 2.40 Gb |
| 100 000 000 | 24 Gb |
Class
Программисты, которым нравится объектно-ориентированное программирование, предпочитают упаковывать данные в класс. Используйте класс, чтобы использовать то же требование:
class Point:
#
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
>>> ob = Point(1,2,3)
Структура данных класса сильно отличается от структуры данных Dict. Давайте посмотрим на использование памяти в этом случае:
| поле | используется внутренняя память |
|---|---|
| PyGC_Head | 24 |
| PyObject_HEAD | 16 |
| __weakref__ | 8 |
| __dict__ | 8 |
| TOTAL | 56 |
О __weakref__ (слабая ссылка) можете проверить этоДокументация, объектdictНекоторые вещи из self.xxx хранятся в . Начиная с Python 3.3, ключ использует хранилище в общей памяти, что уменьшает размер отслеживания экземпляров в ОЗУ.
>>> print(sys.getsizeof(ob), sys.getsizeof(ob.__dict__))
56 112
| Объем данных | используется внутренняя память |
|---|---|
| 1 000 000 | 168 Mb |
| 10 000 000 | 1.68 Gb |
| 100 000 000 | 16.8 Gb |
Вы можете видеть объем памяти, class немного меньше, чем dict, но этого далеко не достаточно.
__slots__
Из распределения памяти, занимаемой классом, мы можем найти это, исключивdictи _weakref__, который может значительно уменьшить размер экземпляров класса в оперативной памяти, мы можем сделать это с помощьюslotsдля достижения этой цели.
class Point:
__slots__ = 'x', 'y', 'z'
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
>>> ob = Point(1,2,3)
>>> print(sys.getsizeof(ob))
64
Видно, что объем памяти значительно уменьшился.
| поле | использование памяти |
|---|---|
| PyGC_Head | 24 |
| PyObject_HEAD | 16 |
| x | 8 |
| y | 8 |
| z | 8 |
| TOTAL | 64 |
| Объем данных | используется внутренняя память |
|---|---|
| 1 000 000 | 64Mb |
| 10 000 000 | 640Mb |
| 100 000 000 | 6.4Gb |
По умолчанию экземпляры классов нового и классического стиля Python имеют словарь для хранения атрибутов экземпляра. В целом это хорошо, и это очень гибко, и вы даже можете установить новые свойства по желанию в программе. Однако для некоторых небольших классов, которые, как известно, имеют несколько фиксированных атрибутов перед «компиляцией», этот dict является пустой тратой памяти.
Эта проблема становится особенно острой, когда необходимо создать большое количество экземпляров. Обходной путь заключается в том, чтобы определитьslotsАтрибуты.
slotsВключите в объявление несколько переменных экземпляра и зарезервируйте достаточно места для каждого экземпляра для хранения каждой переменной; это сэкономит место, так как больше не будет использоваться dict.
Так нужно ли использовать слот? использоватьslotsЕсть и побочные эффекты:
- Каждый унаследованный подкласс должен быть переопределенslots
- Экземпляр может содержать только то, что находится вslotsОпределенные атрибуты, которые влияют на гибкость написания программ.Например, если вы по какой-то причине установили новый атрибут instance, например instance.a = 1, но поскольку a не находится вslotsОн сообщит об ошибке напрямую, вы должны продолжать его изменятьslotsИли используйте другие обходные решения
- Экземпляры не могут иметь цели weakref, в противном случае не забудьте поставитьweakrefвставитьslots
Наконец,namedlistиattrsОбеспечивает автоматическое создание бэндовslotкласс, вы можете попробовать, если вам интересно.
Tuple
Python также имеет встроенный кортеж типов для представления неизменяемых структур данных. Кортежи представляют собой фиксированные структуры или записи, но не имеют имен полей. Для доступа к полю используйте индекс поля. При создании экземпляра кортежа поля кортежа связываются с объектом-значением один раз:
>>> ob = (1,2,3)
>>> x = ob[0]
>>> ob[1] = y # ERROR
Пример кортежа краток:
>>> print(sys.getsizeof(ob))
72
Его можно увидеть толькоslotБолее 8 байт:
| поле | Занятая память (байт) |
|---|---|
| PyGC_Head | 24 |
| PyObject_HEAD | 16 |
| ob_size | 8 |
| [0] | 8 |
| [1] | 8 |
| [2] | 8 |
| TOTAL | 72 |
Namedtuple
Через namedtuple мы также можем получить доступ к элементам в кортеже через значение ключа:
Point = namedtuple('Point', ('x', 'y', 'z'))
Он создает подкласс кортежа, определяющий дескрипторы для доступа к полям по имени. Для нашего примера это выглядит так:
class Point(tuple):
#
@property
def _get_x(self):
return self[0]
@property
def _get_y(self):
return self[1]
@property
def _get_y(self):
return self[2]
#
def __new__(cls, x, y, z):
return tuple.__new__(cls, (x, y, z))
Все экземпляры этого класса занимают тот же объем памяти, что и кортежи. Большое количество экземпляров оставляет немного больший объем памяти:
| Объем данных | использование памяти |
|---|---|
| 1 000 000 | 72 Mb |
| 10 000 000 | 720 Mb |
| 100 000 000 | 7.2 Gb |
Recordclass
Сторонняя библиотека Python RecordClassd предоставляет структуру данных recordclass.mutabletuple, которая почти аналогична встроенной структуре данных кортежа, но занимает меньше памяти.
>>> Point = recordclass('Point', ('x', 'y', 'z'))
>>> ob = Point(1, 2, 3)
После создания отсутствует только PyGC_Head:
| поле | используется внутренняя память |
|---|---|
| PyObject_HEAD | 16 |
| ob_size | 8 |
| x | 8 |
| y | 8 |
| y | 8 |
| TOTAL | 48 |
Пока мы это видим, иslotчем, и еще больше уменьшил объем памяти:
| Объем данных | использование памяти |
|---|---|
| 1 000 000 | 48 Mb |
| 10 000 000 | 480 Mb |
| 100 000 000 | 4.8 Gb |
Dataobject
Recordclass предоставляет другое решение: использовать в памяти сslotsКласс такой же структуры хранения, но не участвует в механизме циклической сборки мусора. Такой экземпляр можно создать через recordclass.make_dataclass:
>>> Point = make_dataclass('Point', ('x', 'y', 'z'))
Другой метод - наследовать от объекта данных
class Point(dataobject):
x:int
y:int
z:int
Созданные таким образом классы будут создавать экземпляры, не участвующие в механизме циклической сборки мусора. Структура экземпляра в памяти такая же, какslotsТа же ситуация, но без PyGC_Head:
| поле | Использование памяти (байты) |
|---|---|
| PyObject_HEAD | 16 |
| x | 8 |
| y | 8 |
| y | 8 |
| TOTAL | 40 |
>>> ob = Point(1,2,3)
>>> print(sys.getsizeof(ob))
40
Для доступа к этим полям также используются специальные дескрипторы для доступа к полям по их смещениям от начала объектов, которые находятся в словаре класса:
mappingproxy({'__new__': <staticmethod at 0x7f203c4e6be0>,
.......................................
'x': <recordclass.dataobject.dataslotgetset at 0x7f203c55c690>,
'y': <recordclass.dataobject.dataslotgetset at 0x7f203c55c670>,
'z': <recordclass.dataobject.dataslotgetset at 0x7f203c55c410>})
| Объем данных | использование памяти |
|---|---|
| 1 000 000 | 40 Mb |
| 10 000 000 | 400 Mb |
| 100 000 000 | 4.0 Gb |
Cython
Существует метод, основанный наCythonиспользование. Его преимущество в том, что поля могут принимать значения атомарных типов языка Си. Например:
cdef class Python:
cdef public int x, y, z
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
В этом случае объем памяти меньше:
>>> ob = Point(1,2,3)
>>> print(sys.getsizeof(ob))
32
Структура памяти распределена следующим образом:
| поле | Использование памяти (байты) |
|---|---|
| PyObject_HEAD | 16 |
| x | 4 |
| y | 4 |
| y | 4 |
| пусто | 4 |
| TOTAL | 32 |
| Объем данных | использование памяти |
|---|---|
| 1 000 000 | 32 Mb |
| 10 000 000 | 320 Mb |
| 100 000 000 | 3.2 Gb |
Однако при доступе из кода Python преобразование из int в объект Python и наоборот выполняется каждый раз.
Numpy
В чистой среде Python использование Numpy может принести лучшие результаты, например:
>>> Point = numpy.dtype(('x', numpy.int32), ('y', numpy.int32), ('z', numpy.int32)])
Создайте массив с начальным значением 0:
>>> points = numpy.zeros(N, dtype=Point)
| Объем данных | использование памяти |
|---|---|
| 1 000 000 | 12 Mb |
| 10 000 000 | 120 Mb |
| 100 000 000 | 1.2 Gb |
Наконец
Видно, что еще многое предстоит сделать с точки зрения оптимизации производительности Python. Хотя Python обеспечивает удобство, он также временно требует больше ресурсов. В разных сценариях мне нужно выбирать разные методы обработки, чтобы повысить производительность.