python - एक कक्षा का उपयोग करने के लिए कौन से संसाधन उपयोग करते हैं?




class memory-management (3)

एक कक्षा के नए बनाए गए उदाहरण के लिए संसाधनों का आवंटन करते समय अजगर (cpython मुझे लगता है) कितना कुशल है? मेरे पास एक ऐसी स्थिति है जहां मुझे एक पेड़ की संरचना बनाने के लिए लाखों बार नोड क्लास की आवश्यकता होगी। नोड ऑब्जेक्ट्स में से प्रत्येक को हल्का होना चाहिए, बस कुछ संख्याओं और माता-पिता और बच्चे के नोड्स का संदर्भ होना चाहिए

उदाहरण के लिए, क्या अजगर को प्रत्येक तात्कालिक वस्तु के सभी "डबल अंडरस्कोर" गुणों के लिए मेमोरी आवंटित करने की आवश्यकता होगी (उदाहरण के लिए __dict__ , __class__ , __class__ , __class__ , आदि), या तो इन गुणों को व्यक्तिगत रूप से बनाने के लिए या जहां वे हैं, वहां स्टोर पॉइंटर्स बनाने के लिए। वर्ग द्वारा परिभाषित? या यह कुशल है और मेरे द्वारा परिभाषित किए गए कस्टम सामान के अलावा कुछ भी स्टोर करने की आवश्यकता नहीं है जिसे प्रत्येक ऑब्जेक्ट में संग्रहीत करने की आवश्यकता है?


क्या यह कुशल है और मेरे द्वारा परिभाषित किए गए कस्टम सामान के अलावा कुछ भी स्टोर करने की आवश्यकता नहीं है जिसे प्रत्येक ऑब्जेक्ट में संग्रहीत करने की आवश्यकता है?

लगभग हाँ, कुछ निश्चित स्थान को छोड़कर। पायथन में कक्षा पहले से ही एक type उदाहरण है, जिसे मेटाक्लास कहा जाता है। जब क्लास ऑब्जेक्ट का नया उदाहरण, custom stuff सिर्फ __init__ में वे चीजें हैं। कक्षा में परिभाषित विशेषताएँ और विधियाँ अधिक स्थान नहीं खर्च करेंगी।

कुछ निश्चित स्थान के लिए, बस Reblochon Masque के उत्तर को देखें, बहुत अच्छा और प्रभावशाली।

शायद मैं एक सरल लेकिन उदाहरण दे सकता हूं:

class T(object):
    def a(self):
        print(self)
t = T()
t.a()
# output: <__main__.T object at 0x1060712e8>
T.a(t)
# output: <__main__.T object at 0x1060712e8>
# as you see, t.a() equals T.a(t)

import sys
sys.getsizeof(T)
# output: 1056
sys.getsizeof(T())
# output: 56

सतही तौर पर यह काफी सरल है: तरीके, वर्ग चर, और वर्ग डॉकस्ट्रिंग को कक्षा में संग्रहित किया जाता है (फ़ंक्शन में फ़ंक्शन फ़ंक्शन के रूप में संग्रहीत किया जाता है)। इंस्टेंस चर उदाहरण में संग्रहीत किए जाते हैं। उदाहरण वर्ग को भी संदर्भित करता है ताकि आप विधियों को देख सकें। आमतौर पर उनमें से सभी को शब्दकोशों में संग्रहीत किया जाता है ( __dict__ )।

तो हाँ, संक्षिप्त उत्तर है: पायथन इंस्टेंस में तरीकों को संग्रहीत नहीं करता है, लेकिन सभी उदाहरणों को कक्षा का संदर्भ होना चाहिए।

उदाहरण के लिए यदि आपके पास एक साधारण वर्ग है जैसे:

class MyClass:
    def __init__(self):
        self.a = 1
        self.b = 2

    def __repr__(self):
        return f"{self.__class__.__name__}({self.a}, {self.b})"

instance_1 = MyClass()
instance_2 = MyClass()

तब इन-मेमोरी में यह (बहुत सरलीकृत) दिखता है:

गहराई तक जा रहे हैं

हालांकि कुछ चीजें हैं जो सीपीथॉन में गहराई से जाने पर महत्वपूर्ण हैं:

  • अमूर्त के रूप में एक शब्दकोश होने के कारण ओवरहेड का एक सा हो जाता है: आपको उदाहरण शब्दकोश (बाइट्स) के संदर्भ की आवश्यकता होती है और शब्दकोश में प्रत्येक प्रविष्टि में हैश (8bytes), एक सूचक को कुंजी (8bytes) और एक सूचक को स्टोर करता है। संग्रहीत विशेषता (एक और 8 बाइट्स)। आम तौर पर शब्दकोशों को अधिक-आबंटित किया जाता है ताकि एक और विशेषता जोड़ने से शब्दकोश-आकार को ट्रिगर न किया जा सके।
  • पायथन में "मूल्य-प्रकार" नहीं हैं, यहां तक ​​कि एक पूर्णांक भी एक उदाहरण होगा। इसका मतलब है कि पूर्णांक को संग्रहीत करने के लिए आपको 4 बाइट्स की आवश्यकता नहीं है - पूर्णांक को संग्रहीत करने के लिए अजगर (मेरे कंप्यूटर पर) 24bytes 0 से और पूर्णांक को शून्य से अलग करने के लिए कम से कम 28 बाइट को संग्रहीत करने की आवश्यकता है। हालाँकि अन्य वस्तुओं के संदर्भ में केवल 8 बाइट्स (पॉइंटर) की आवश्यकता होती है।
  • CPython संदर्भ गिनती का उपयोग करता है इसलिए प्रत्येक उदाहरण को संदर्भ गणना (8bytes) की आवश्यकता होती है। इसके अलावा, अधिकांश CPythons वर्ग चक्रीय कचरा संग्रहकर्ता में भाग लेते हैं, जो प्रति उदाहरण एक अन्य 24bytes के ओवरहेड को लगाता है। इन वर्गों के अलावा जो कमजोर-संदर्भित हो सकते हैं (उनमें से अधिकांश) में __weakref__ फ़ील्ड (एक और 8 बाइट्स) भी होती है।

इस बिंदु पर यह बताना भी आवश्यक है कि सीपीथॉन इनमें से कुछ "समस्याओं" के लिए अनुकूलन करता है:

  • उदाहरण के शब्दकोशों के कुछ मेमोरी ओवरहेड्स (हैश और की) से बचने के लिए पायथन कुंजी-साझाकरण शब्दकोश का उपयोग करता है।
  • __dict__ और __weakref__ से बचने के लिए आप कक्षाओं में __slots__ का उपयोग कर सकते हैं। यह प्रति उदाहरण काफी कम मेमोरी-फ़ुटप्रिंट दे सकता है।
  • पाइथन कुछ मूल्यों को बढ़ाता है, उदाहरण के लिए यदि आप एक छोटा पूर्णांक बनाते हैं तो यह एक नया पूर्णांक उदाहरण नहीं बनाएगा, लेकिन पहले से मौजूद उदाहरण के लिए एक संदर्भ लौटाता है।

यह देखते हुए कि इनमें से कई और (विशेषकर अनुकूलन के बारे में अंक) कार्यान्वयन-विवरण हैं, यह पायथन कक्षाओं की प्रभावी स्मृति-आवश्यकताओं के बारे में एक कैनोनिकल उत्तर देना कठिन है।

उदाहरणों की स्मृति पदचिह्न को कम करना

हालाँकि, यदि आप अपने उदाहरणों की मेमोरी-फ़ुटप्रिंट को कम करना चाहते हैं तो निश्चित रूप से __slots__ को आज़माएं। उनके पास ड्रॉ बैक हैं लेकिन अगर वे आपके लिए लागू नहीं होते हैं तो वे मेमोरी को कम करने का एक बहुत अच्छा तरीका है।

class Slotted:
    __slots__ = ('a', 'b')
    def __init__(self):
        self.a = 1
        self.b = 1

यदि यह पर्याप्त नहीं है और आप बहुत सारे "मूल्य प्रकारों" के साथ काम करते हैं, तो आप एक कदम आगे भी बढ़ सकते हैं और विस्तार कक्षाएं बना सकते हैं। ये ऐसी कक्षाएं हैं जो सी में परिभाषित हैं लेकिन लिपटे हुए हैं ताकि आप उन्हें पायथन में उपयोग कर सकें।

सुविधा के लिए मैं साइथॉन के लिए IPython बाइंडिंग का उपयोग यहां एक विस्तार वर्ग का अनुकरण करने के लिए कर रहा हूं:

%load_ext cython
%%cython

cdef class Extensioned:
    cdef long long a
    cdef long long b

    def __init__(self):
        self.a = 1
        self.b = 1

मेमोरी उपयोग को मापने

इस सभी सिद्धांत के बाद शेष दिलचस्प सवाल यह है: हम स्मृति को कैसे माप सकते हैं?

मैं भी एक सामान्य वर्ग का उपयोग करता हूं:

class Dicted:
    def __init__(self):
        self.a = 1
        self.b = 1

मैं आमतौर पर मेमोरी प्रभाव को मापने के लिए psutil (भले ही यह एक प्रॉक्सी विधि है) का उपयोग कर रहा हूं और बस यह psutil हूं कि यह पहले और बाद में कितनी मेमोरी का उपयोग करता था। माप थोड़ा ऑफसेट हैं क्योंकि मुझे किसी भी तरह स्मृति में उदाहरण रखने की आवश्यकता है, अन्यथा स्मृति को पुनः प्राप्त किया जाएगा (तुरंत)। इसके अलावा यह केवल एक सन्निकटन है क्योंकि पायथन वास्तव में मेमोरी हाउसकीपिंग का एक सा करता है, खासकर जब बहुत सारे क्रिएट / डिलीट होते हैं।


import os
import psutil
process = psutil.Process(os.getpid())

runs = 10
instances = 100_000

memory_dicted = [0] * runs
memory_slotted = [0] * runs
memory_extensioned = [0] * runs

for run_index in range(runs):
    for store, cls in [(memory_dicted, Dicted), (memory_slotted, Slotted), (memory_extensioned, Extensioned)]:
        before = process.memory_info().rss
        l = [cls() for _ in range(instances)]
        store[run_index] = process.memory_info().rss - before
        l.clear()  # reclaim memory for instances immediately

प्रत्येक रन के लिए मेमोरी बिल्कुल समान नहीं होगी क्योंकि पायथन कुछ मेमोरी का फिर से उपयोग करता है और कभी-कभी अन्य उद्देश्यों के लिए भी मेमोरी को इधर-उधर रखता है लेकिन इसे कम से कम एक उचित संकेत देना चाहिए:

>>> min(memory_dicted) / 1024**2, min(memory_slotted) / 1024**2, min(memory_extensioned) / 1024**2
(15.625, 5.3359375, 2.7265625)

मैंने यहां min का उपयोग ज्यादातर इसलिए किया क्योंकि मुझे दिलचस्पी थी कि न्यूनतम क्या था और मैंने बाइट्स को मेगाबाइट्स में बदलने के लिए 1024**2 से विभाजित किया।

सारांश: अपेक्षा के अनुसार सामान्य वर्ग के लिए स्लॉट के साथ कक्षाओं की तुलना में अधिक मेमोरी की आवश्यकता होगी, लेकिन विस्तार वर्ग (यदि लागू हो और उपलब्ध हो) में एक भी कम स्मृति पदचिह्न हो सकता है।

एक अन्य उपकरण जो मेमोरी उपयोग को मापने के लिए बहुत उपयोगी हो सकता है, वह है memory_profiler , हालांकि मैंने थोड़ी देर में इसका उपयोग नहीं किया है।


[संपादित करें] एक अजगर प्रक्रिया द्वारा स्मृति उपयोग का सटीक माप प्राप्त करना आसान नहीं है; मुझे नहीं लगता कि मेरा जवाब पूरी तरह से सवाल का जवाब देता है , लेकिन यह एक दृष्टिकोण है जो कुछ मामलों में उपयोगी हो सकता है।

अधिकांश दृष्टिकोण प्रॉक्सी विधियों का उपयोग करते हैं (एन ऑब्जेक्ट बनाते हैं और सिस्टम मेमोरी पर प्रभाव का अनुमान लगाते हैं), और बाहरी लाइब्रेरी उन तरीकों को लपेटने का प्रयास करते हैं। उदाहरण के लिए, धागे here , here और there [/ संपादित करें]

cPython 3.7 , एक नियमित वर्ग के उदाहरण का न्यूनतम आकार 56 बाइट्स है; __slots__ (कोई शब्दकोश नहीं) के साथ, 16 बाइट्स।

import sys

class A:
    pass

class B:
    __slots__ = ()
    pass

a = A()
b = B()
sys.getsizeof(a), sys.getsizeof(b)

उत्पादन:

56, 16

उदाहरण के स्तर पर Docstrings, वर्ग चर, और प्रकार एनोटेशन नहीं पाए जाते हैं:

import sys

class A:
    """regular class"""
    a: int = 12

class B:
    """slotted class"""
    b: int = 12
    __slots__ = ()

a = A()
b = B()
sys.getsizeof(a), sys.getsizeof(b)

उत्पादन:

56, 16

[संपादित करें] इसके अलावा, वर्ग परिभाषा के आकार के माप के लिए @LiuXiMin उत्तर देखें। [/ संपादित करें]








cpython