Вчера я был занят, помогая молодому инженеру в нашем стартапе решить проблему техники о странно высоком потреблении оперативной памяти. Недавно он внедрил API в нашем проекте Back-End для загрузки данных из json/jsonl Файл в mongodb. Вчера я провел некоторое время, когда копаться в потреблении барана на Python. Я хотел бы поделиться тем, что я узнал по этому вопросу.
Проверьте состояние RAM Linux
Прежде всего, я хочу отметить, как я проверяю использование оперативной памяти в нашем Ubuntu 18.04, работающем на сервере.
free -h
Вывод в моем случае:
Некоторое объяснение вывода бесплатно
команда из Эта страница :
Бесплатно Память – это объем памяти, который в настоящее время не используется ни для чего. Это число должно быть небольшим, потому что память, которая не используется, просто потрачена впустую.
Доступно Память – это объем памяти, который доступен для распределения нового процесса или существующих процессов.
Распределение памяти в C ++
Я хотел бы вспомнить, как C ++ выделяет воспоминания о своих переменных. В C ++ (до C ++ 11) все переменные объявляются с предопределенным типом. Таким образом, компилятор может легко определить размер переменной и где ее хранить (куча, стек или статическая область). Смотрите этот пример, который я написал вчера (у меня есть 64-битный процессор, а компилятор-G ++ для x86_64) в качестве файла testmem.cpp
:
#include/* I set here alignment to 1 byte. One can remove this line to see RAM consumption with default alignment setting /* #pragma pack(1) using namespace std; int g1 = 4; class c1 { }; class c2 { int x = 1; int y = 2; char z = 12; char* name; }; int main() { cout << " ================= " << endl; cout << " Sizeof g1: " << sizeof(g1) << endl; cout << " Address of g1: " << &(g1) << endl; cout << " ================= " << endl; int a = 100; double b = 20.0; c1* myC1 = new c1(); // heap c2* myC2 = new c2(); // heap char c = 55; short d = 122; cout << " Sizeof a: " << sizeof(a) << endl; cout << " Address of a: " << &(a) << endl; cout << " Sizeof b: " << sizeof(b) << endl; cout << " Address of b: " << &(b) << endl; cout << " Sizeof c: " << sizeof(c) << endl; cout << " Address of c: " << static_cast (&c) << endl; cout << " Sizeof d: " << sizeof(d) << endl; cout << " Address of d: " << static_cast (&d) << endl; cout << " ================= " << endl; cout << " Sizeof c1: " << sizeof(c1) << endl; cout << " Sizeof c2: " << sizeof(c2) << endl; cout << " Sizeof myC1: " << sizeof(myC1) << endl; cout << " Sizeof myC2: " << sizeof(myC2) << endl; cout << " ================= " << endl; cout << " Address of ptr myC1: " << static_cast (&myC1) << endl; cout << " Address of ptr myC2: " << static_cast (&myC1) << endl; cout << " Address value of myC1: " << static_cast (myC1) << endl; // heap cout << " Address value of myC2: " << static_cast (myC1) << endl; // heap cout << " ================= " << endl; int arr[10] = {1}; cout << " Sizeof arr: " << sizeof(arr) << endl; // array of 10 integers cout << " Address of arr: " << arr << endl; }
Скомпилируйте этот файл и выполните его:
> g++ testMem.cpp -o testMem > ./testMem
Ниже приведен выход:
В C ++ нам совершенно ясно предсказать, в какой области памяти (стека/куча/статика) переменная сохраняется только путем чтения кода. Размер простой переменной в C ++ – это именно то количество байтов, в которых хранится ее данные, а также прямо для вычисления размера переменной типа составного типа. Как показано в этом примере, можно рассчитать размер класса/структуры, суммируя размеры его нестатических элементов данных (можно искать в Google дальнейшее объяснение).
Распределение памяти в Python
Теперь давайте сделаем несколько тестов на Python. Мы можем использовать sys.getSizeof ()
и id ()
Однако, чтобы получить размер и адрес объекта в Python, реальный расчет потребления оперативной памяти в Python немного сложнее, чем ожидалось.
import sys import time def testMem(): a1, a2 = 1, 1.0 print("++ a1 has size: {}, address: {}".format( sys.getsizeof(a1), id(a1) )) print("-- a2 has size: {}, address: {}".format( sys.getsizeof(a2), id(a2) )) b1, b2 = 256, 257 print("++ b1 has size: {}, address: {}".format( sys.getsizeof(b1), id(b1) )) print("-- b2 has size: {}, address: {}".format( sys.getsizeof(b2), id(b2) )) c1, c2 = -5, -6 print("++ c1 has size: {}, address: {}".format( sys.getsizeof(c1), id(c1) )) print("-- c2 has size: {}, address: {}".format( sys.getsizeof(c2), id(c2) )) d1 = {"x":12} d2 = {"x1":100000, "x2":"abcdefg", "x3":-100000000000, "x4":0.00000005, "x5": 'v'} print("++ d1 has size: {}, address: {}".format( sys.getsizeof(d1), id(d1) )) print("-- d2 has size: {}, address: {}".format( sys.getsizeof(d2), id(d2) )) e1 = (1, 2, 3) e2 = [1, 2, 3] print("++ e1 has size: {}, address: {}".format( sys.getsizeof(e1), id(e1) )) print("-- e2 has size: {}, address: {}".format( sys.getsizeof(e2), id(e2) )) if __name__ =="__main__": testMem()
Вывод выполнения:
Как мы можем видеть на рисунке выше, размеры переменных в Python больше, чем в C ++. Причина этого факта заключается в том, что все в Python является объектом (то есть экземпляром класса). Некоторые интересные факты, замеченные в этом примере:
- Адреса целых чисел в [-5, 256] находятся далеко от адресов других целых чисел (-6, 257 в этом примере)
- Короткий дикт имеет тот же размер, что и длинный дикт
- Размер кортежа/списка не является суммой всех его элементов
Чтобы понять детали управления памятью в Python, можно обратиться к Эта статья Анкет Здесь я хочу подчеркнуть две важные вещи:
- Управление Python сильно отличается от C ++, объекты Python имеют огромные фиксированные накладные расходы в отношении C ++.
- Для контейнеров Python,
sys.getSizeof ()
Однако не возвращает сумму его содержащих объектов, он возвращает только потребление памяти самого контейнера и указатели к его объектам.
Ниже приведен пример для расчета «реального размера» контейнера. Эта функция total_size ()
Пройдет все элементы в контейнере и подведите сумму их размеров, чтобы дать общий размер контейнера. Смотрите код:
from __future__ import print_function from sys import getsizeof, stderr from itertools import chain from collections import deque try: from reprlib import repr except ImportError: pass import sys def total_size(o, handlers={}, verbose=False): """ Returns the approximate memory footprint an object and all of its contents. Automatically finds the contents of the following builtin containers and their subclasses: tuple, list, deque, dict, set and frozenset. To search other containers, add handlers to iterate over their contents: handlers = {SomeContainerClass: iter, OtherContainerClass: OtherContainerClass.get_elements} """ dict_handler = lambda d: chain.from_iterable(d.items()) all_handlers = {tuple: iter, list: iter, deque: iter, dict: dict_handler, set: iter, frozenset: iter, } all_handlers.update(handlers) # user handlers take precedence seen = set() # track which object id's have already been seen default_size = getsizeof(0) # estimate sizeof object without __sizeof__ def sizeof(o): if id(o) in seen: # do not double count the same object return 0 seen.add(id(o)) s = getsizeof(o, default_size) if verbose: print(s, type(o), repr(o), file=stderr) for typ, handler in all_handlers.items(): if isinstance(o, typ): s += sum(map(sizeof, handler(o))) break return s return sizeof(o) def testMemory(): a = {"x":12} b = {"x1":1, "x2":"hello", "x3":1.2, "x4":-3, "x5":2000000} print("memory of a: {}".format( total_size(a) )) print("memory of b: {}".format( total_size(b) )) print('Done!') if __name__ == '__main__': testMemory()
Вывод выполнения:
Наша проблема
Чтобы проанализировать нашу проблему техники, я использовал очень удобный инструмент memory_profiler Для отображения состояния потребления памяти. Вот мой тестовый код (функция total_size ()
необходимо, но не показано здесь):
from pymongo import MongoClient from memory_profiler import profile import sys @profile def testMemory(): client = MongoClient("mongodb://xxxx:tttttt@ourserver:8917") db = client["suppliers"] col = db.get_collection("companies_pool") re_query_filter = {"domain": {'$regex': "科技"} } docs = col.find(re_query_filter) print(type(docs)) docs = docs[10:] l = list(docs) print("memory of l: {}".format( total_size(l) )) f = open("D:\\concour_s2\\Train\\dd.zip", "br") // a large file in my test, in server case, it shall load only json/jsonl file s = f.read() print("memory of s: ", sys.getsizeof(s)) del l del s print('Done!') if __name__ == '__main__': testMemory()
Ниже приведен выход выполнения:
Код в этой части не то, что наш инженер написал в своем проекте, но он реализовал некоторые аналогичные операции в своем методе API. С помощью этого простого примера он теперь понял, почему его метод иногда пожирал на удивление так много оперативной памяти. Тогда проблема была быстро решена.
Оригинал: “https://dev.to/jemaloqiu/ram-consumption-in-python-1e0d”