PythonでのRAM消費


昨日、私たちのスタートアップの若い技術者が、驚くべき高いRAM消費についての技術的問題を解決するのに忙しかったです.彼は最近、JSON/JSONLファイルからMongoDBにデータをアップロードするためのバックエンドプロジェクトにAPIを実装しました.私は昨日、PythonのRAM消費を掘り下げて、いくつかの時間を過ごした.私はこの主題のために学んだことを共有したいと思います.

チェックRAMステータス


とりわけ、私はどのように私はサーバー上で実行している私たちのUbuntu 18.04のRAMの使用方法をチェックしてください.
free -h
私の場合の出力

出力の説明free コマンドthis page :

Free memory is the amount of memory which is currently not used for anything. This number should be small, because memory which is not used is simply wasted.

Available memory is the amount of memory which is available for allocation to a new process or to existing processes.


C ++におけるメモリ割り当て


C +がどのようにその変数のためにメモリを割り当てるかを最初に思い出したいです.C +では(C++ 11以前)、すべての変数は定義済みの型で宣言されます.このように、コンパイラは容易に変数のサイズを決めることができます、そして、どこでそれを格納するか(ヒープ、スタックまたは静的な領域).私が昨日書いたこの例を見てください(私は64ビットCPUを持っていて、コンパイラはx 86testMem.cpp :
#include <iostream>
/* 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<void *>(&c) << endl;

    cout << " Sizeof d: " << sizeof(d) << endl;
    cout << " Address of d: " << static_cast<void *>(&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<void *>(&myC1) << endl;
    cout << " Address of ptr myC2: " << static_cast<void *>(&myC1) << endl;

    cout << " Address value of myC1: " << static_cast<void *>(myC1) << endl; // heap
    cout << " Address value of myC2: " << static_cast<void *>(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 +の単純な変数のサイズは、そのデータが格納されているバイト数である.また、データ型変数のサイズを計算するためにも直進する.この例に示すように、非静的データメンバーのサイズを合計することによってクラス/構造体のサイズを計算することができます(1つは、より詳細な説明のためにGoogleを検索することができます).

Pythonでのメモリ割り当て


では、Pythonでテストを行いましょう.私たちはsys.getsizeof() and id() Pythonでオブジェクトのサイズとアドレスを取得するには、PythonでのRAM消費の実際の計算は予想よりも少し複雑です.
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).
  • 短いdictは、長いdictと同じサイズです
  • タプル/リストのサイズはすべての項目の合計ではありません
  • Pythonでのメモリ管理の詳細を理解するにはthis article . ここでは二つの重要なことを強調したい.
  • 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()
    
    実行出力

    我々の問題


    私たちのテクニックの問題を分析するために、私は非常に便利なツールMemoryYounterプロファイラを使用してメモリの消費状況を表示します.これが私のテストコードです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メソッドで同様の操作を実装しました.この簡単な例では、彼はなぜ彼の方法は驚くほど非常にラムを時折悪魔を理解している.その後、問題はすぐに解決されている.