Рубрики
Без рубрики

Потребление оперативной памяти в Python

Вчера я был занят, помогая молодому инженеру в нашем стартапе решить проблему техники о Стране … Tagged с Python, Julia, Memory, Siziof.

Вчера я был занят, помогая молодому инженеру в нашем стартапе решить проблему техники о странно высоком потреблении оперативной памяти. Недавно он внедрил 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”