पायथन में स्मृति को जारी करना


128

निम्नलिखित उदाहरण में स्मृति उपयोग के संबंध में मेरे कुछ संबंधित प्रश्न हैं।

  1. अगर मैं दुभाषिया में चला,

    foo = ['bar' for _ in xrange(10000000)]

    मेरी मशीन पर उपयोग की जाने वाली वास्तविक मेमोरी तक जाती है 80.9mb। मैं फिर,

    del foo

    वास्तविक स्मृति नीचे जाती है, लेकिन केवल करने के लिए 30.4mb। दुभाषिया 4.4mbआधार रेखा का उपयोग करता है तो 26mbओएस को मेमोरी जारी न करने में क्या फायदा है ? क्या यह इसलिए है क्योंकि पायथन "आगे की योजना बना रहा है", यह सोचकर कि आप फिर से उस स्मृति का उपयोग कर सकते हैं?

  2. यह 50.5mbविशेष रूप से क्यों जारी करता है - वह राशि क्या है जो इसके आधार पर जारी की जाती है?

  3. क्या पायथन को सभी मेमोरी को जारी करने के लिए मजबूर करने का एक तरीका है (यदि आप जानते हैं कि आप फिर से उस मेमोरी का उपयोग नहीं करेंगे)?

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


4
यह ध्यान देने योग्य है कि यह व्यवहार पायथन के लिए विशिष्ट नहीं है। यह आम तौर पर ऐसा होता है कि, जब कोई प्रक्रिया कुछ ढेर-आवंटित मेमोरी को मुक्त करती है, तो स्मृति ओएस पर वापस नहीं जाती है जब तक कि प्रक्रिया समाप्त नहीं हो जाती।
NPE

आपका प्रश्न कई चीजें पूछता है - जिनमें से कुछ दुपट्टे हैं, जिनमें से कुछ SO के लिए अनुपयुक्त हैं, जिनमें से कुछ अच्छे प्रश्न हो सकते हैं। क्या आप पूछ रहे हैं कि क्या पाइथन मेमोरी को रिलीज़ नहीं करता है, इसके तहत वह किन परिस्थितियों में कर सकता है / नहीं कर सकता है, अंतर्निहित तंत्र क्या है, क्यों इसे इस तरह से डिज़ाइन किया गया था, क्या कोई वर्कअराउंड हैं, या पूरी तरह से कुछ और हैं?
abarnert

2
@abarnert I ने उन सबक्वेस्टेशन्स को मिलाया जो समान थे। आपके सवालों का जवाब देने के लिए: मुझे पता है कि पायथन ओएस को कुछ मेमोरी जारी करता है, लेकिन यह सब क्यों नहीं और जो राशि है वह क्यों करता है। यदि ऐसी परिस्थितियाँ हैं जहाँ यह नहीं हो सकता है, तो क्यों? क्या workarounds के रूप में अच्छी तरह से।
जारेड


@jww मुझे ऐसा नहीं लगता। यह सवाल वास्तव में इस बात से संबंधित है कि कॉल के साथ पूरी तरह से कचरा इकट्ठा करने के बाद भी दुभाषिया प्रक्रिया ने मेमोरी क्यों जारी नहीं की gc.collect
जारेड

जवाबों:


86

ढेर पर आवंटित मेमोरी उच्च-पानी के निशान के अधीन हो सकती है। यह PyObject_Malloc4 कीबी पूल में छोटी वस्तुओं ( ) को आवंटित करने के लिए पायथन की आंतरिक अनुकूलन द्वारा जटिल है , 8 बाइट के गुणकों में आवंटन आकार के लिए वर्गीकृत - 256 बाइट्स (3.3 में 512 बाइट्स)। पूल स्वयं 256 KiB एरेनास में हैं, इसलिए यदि केवल एक पूल में एक ब्लॉक का उपयोग किया जाता है, तो पूरे 256 KiB क्षेत्र को जारी नहीं किया जाएगा। पायथन 3.3 में छोटी वस्तु आबंटक को ढेर के बजाय अनाम मेमोरी मैप का उपयोग करने के लिए स्विच किया गया था, इसलिए इसे मेमोरी को रिलीज़ करने में बेहतर प्रदर्शन करना चाहिए।

इसके अतिरिक्त, अंतर्निहित प्रकार पहले से आवंटित वस्तुओं के स्वतंत्र रूप से बनाए रखते हैं जो छोटी वस्तु आवंटनकर्ता का उपयोग कर सकते हैं या नहीं कर सकते हैं। intप्रकार अपनी ही आबंटित स्मृति के साथ एक freelist का कहना है, और उसे साफ़ बुला की आवश्यकता है PyInt_ClearFreeList()। इसे पूर्ण रूप से करके अप्रत्यक्ष रूप से कहा जा सकता है gc.collect

इसे इस तरह आज़माएं, और मुझे बताएं कि आपको क्या मिलता है। यहाँ psutil.Process.memory_info के लिए लिंक है ।

import os
import gc
import psutil

proc = psutil.Process(os.getpid())
gc.collect()
mem0 = proc.get_memory_info().rss

# create approx. 10**7 int objects and pointers
foo = ['abc' for x in range(10**7)]
mem1 = proc.get_memory_info().rss

# unreference, including x == 9999999
del foo, x
mem2 = proc.get_memory_info().rss

# collect() calls PyInt_ClearFreeList()
# or use ctypes: pythonapi.PyInt_ClearFreeList()
gc.collect()
mem3 = proc.get_memory_info().rss

pd = lambda x2, x1: 100.0 * (x2 - x1) / mem0
print "Allocation: %0.2f%%" % pd(mem1, mem0)
print "Unreference: %0.2f%%" % pd(mem2, mem1)
print "Collect: %0.2f%%" % pd(mem3, mem2)
print "Overall: %0.2f%%" % pd(mem3, mem0)

आउटपुट:

Allocation: 3034.36%
Unreference: -752.39%
Collect: -2279.74%
Overall: 2.23%

संपादित करें:

मैंने सिस्टम में अन्य प्रक्रियाओं के प्रभावों को खत्म करने के लिए प्रक्रिया वीएम आकार के सापेक्ष मापने के लिए स्विच किया।

सी रनटाइम (जैसे glibc, msvcrt) ढेर को सिकोड़ता है जब शीर्ष पर सन्निहित मुक्त स्थान एक निरंतर, गतिशील या विन्यास योग्य सीमा तक पहुँच जाता है। malloptGlibc के साथ आप इसे (M_TRIM_THRESHOLD) से ट्यून कर सकते हैं । इसे देखते हुए, यह आश्चर्य की बात नहीं है कि यदि ढेर अधिक सिकुड़ता है - यहां तक ​​कि बहुत अधिक - उस ब्लॉक की तुलना में जो आप free

3.x में rangeकोई सूची नहीं है, इसलिए ऊपर दिए गए परीक्षण में 10 मिलियन intऑब्जेक्ट नहीं बनेंगे। यहां तक ​​कि अगर यह किया है, int3.x में प्रकार मूल रूप से एक 2.x है long, जो एक स्वतंत्र लागू नहीं करता है।


के memory_info()बजाय का उपयोग करें get_memory_info()और xपरिभाषित किया गया है
अजीज ऑल्टो

आप intपायथन 3 में भी 10 ^ 7 एस प्राप्त करते हैं , लेकिन प्रत्येक लूप वेरिएबल में अंतिम को बदल देता है ताकि वे एक साथ
डेविस हेरिंग

मुझे स्मृति रिसाव की समस्या है, और मुझे लगता है कि इसका कारण यह है कि आपने यहाँ क्या उत्तर दिया है। लेकिन मैं अपना अनुमान कैसे साबित कर सकता हूं? क्या कोई उपकरण है जो दिखा सकता है कि कई पूल मलोक्ड हैं, लेकिन केवल एक छोटा सा ब्लॉक उपयोग किया जाता है?
ruiruige1991

130

मैं उस प्रश्न का अनुमान लगा रहा हूं जो आप वास्तव में यहां ध्यान रखते हैं:

क्या पायथन को सभी मेमोरी को जारी करने के लिए मजबूर करने का एक तरीका है (यदि आप जानते हैं कि आप फिर से उस मेमोरी का उपयोग नहीं करेंगे)?

नहीं वहाँ नहीं है। लेकिन एक आसान समाधान है: बच्चे की प्रक्रिया।

यदि आपको 5 मिनट के लिए 500MB अस्थायी स्टोरेज की आवश्यकता है, लेकिन उसके बाद आपको 2 घंटे तक चलने की आवश्यकता है और उस मेमोरी को कभी भी टच नहीं करेंगे, तो मेमोरी प्रोसेस को करने के लिए एक बच्चे की प्रक्रिया को स्पॉन करें। जब बच्चे की प्रक्रिया समाप्त हो जाती है, तो मेमोरी जारी हो जाती है।

यह पूरी तरह से तुच्छ और मुक्त नहीं है, लेकिन यह बहुत आसान और सस्ता है, जो आमतौर पर व्यापार के लिए काफी अच्छा है।

सबसे पहले, बच्चे की प्रक्रिया बनाने का सबसे आसान तरीका concurrent.futures(या 3.1 और पहले के लिए, futuresPyPI पर बैकपोर्ट है):

with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
    result = executor.submit(func, *args, **kwargs).result()

यदि आपको थोड़ा और नियंत्रण की आवश्यकता है, तो multiprocessingमॉड्यूल का उपयोग करें ।

लागत हैं:

  • प्रक्रिया स्टार्टअप कुछ प्लेटफार्मों पर, विशेष रूप से विंडोज की तरह धीमी है। हम यहां मिलिसेकंड्स बात कर रहे हैं, मिनट नहीं, और यदि आप 300 सेकंड के काम के लिए एक बच्चे को पाल रहे हैं, तो आप इसे नोटिस नहीं करेंगे। लेकिन यह मुफ़्त नहीं है।
  • यदि आपके द्वारा उपयोग की जाने वाली अस्थायी मेमोरी की बड़ी मात्रा बड़ी है , तो ऐसा करने से आपका मुख्य प्रोग्राम स्वैप हो सकता है। बेशक आप लंबे समय में समय बचा रहे हैं, क्योंकि अगर वह स्मृति हमेशा के लिए चारों ओर लटका रही तो उसे किसी बिंदु पर स्वैप करने के लिए नेतृत्व करना होगा। लेकिन यह क्रमिक सुस्ती को कुछ उपयोग के मामलों में बहुत ध्यान देने योग्य सभी-एक बार (और जल्दी) देरी में बदल सकता है।
  • प्रक्रियाओं के बीच बड़ी मात्रा में डेटा भेजना धीमा हो सकता है। फिर, यदि आप तर्क के 2K पर भेजने और 64K परिणाम प्राप्त करने के बारे में बात कर रहे हैं, तो आप इसे नोटिस भी नहीं करेंगे, लेकिन यदि आप बड़ी मात्रा में डेटा भेज रहे हैं और प्राप्त कर रहे हैं, तो आप कुछ अन्य तंत्र का उपयोग करना चाहेंगे (एक फ़ाइल, mmapपैड या अन्यथा; साझा की गई मेमोरी एपीआई इन multiprocessing; आदि)।
  • प्रक्रियाओं के बीच बड़ी मात्रा में डेटा भेजने का मतलब है कि डेटा को प्राप्य होना चाहिए (या, यदि आप उन्हें किसी फ़ाइल में साझा करते हैं या साझा की गई मेमोरी, struct-able या आदर्श रूप से-योग्य ctypes)।

वास्तव में अच्छी चाल है, हालांकि समस्या का समाधान नहीं है :( लेकिन मैं वास्तव में इसे पसंद करता हूं
ddofborg

32

एरिक्सुन ने प्रश्न # 1 का उत्तर दिया है, और मैंने प्रश्न # 3 (मूल # 4) का उत्तर दिया है, लेकिन अब प्रश्न # 2 का उत्तर दें:

यह विशेष रूप से 50.5mb क्यों जारी करता है - वह राशि जो इसके आधार पर जारी की जाती है?

यह किस पर आधारित है, आखिरकार, पायथन के अंदर संयोगों की एक पूरी श्रृंखला है और mallocयह भविष्यवाणी करना बहुत कठिन है।

सबसे पहले, आप स्मृति को कैसे माप रहे हैं, इसके आधार पर, आप केवल उन पृष्ठों को माप सकते हैं जो वास्तव में स्मृति में मैप किए गए हैं। उस स्थिति में, पेजर द्वारा किसी भी समय स्वैप किया जाता है, मेमोरी को "मुक्त" के रूप में दिखाया जाएगा, भले ही इसे मुक्त नहीं किया गया हो।

या आप इन-उपयोग पृष्ठों को माप सकते हैं, जो आवंटित-लेकिन-कभी भी छुआ जाने वाले पृष्ठों की गणना नहीं कर सकते हैं (सिस्टम पर जो कि बेहतर तरीके से आवंटित किए गए हैं, जैसे कि लिनक्स), वे पृष्ठ जो आवंटित किए गए लेकिन टैग किए गए हैं MADV_FREE, आदि।

यदि आप वास्तव में आवंटित पृष्ठों को माप रहे हैं (जो वास्तव में करने के लिए बहुत उपयोगी चीज नहीं है, लेकिन ऐसा लगता है कि आप क्या पूछ रहे हैं), और पृष्ठों को वास्तव में निपटाया गया है, दो परिस्थितियों में ऐसा हो सकता है: या तो आप ' brkडेटा खंड (आजकल बहुत कम) को सिकोड़ने या उपयोग करने के लिए, या आपने munmapमैप किए गए सेगमेंट को जारी करने के लिए उपयोग या समान किया है। (उत्तरार्द्ध में सैद्धांतिक रूप से एक मामूली रूपांतर भी है, जिसमें मैप किए गए सेगमेंट के भाग को जारी करने के तरीके हैं - उदाहरण के लिए, इसे उस सेगमेंट के MAP_FIXEDलिए चुराएं MADV_FREEजिसे आप तुरंत अनमैप करते हैं।)

लेकिन अधिकांश कार्यक्रम सीधे मेमोरी पेजों से चीजों को आवंटित नहीं करते हैं; वे malloc-स्टाइल एलोकेटर का उपयोग करते हैं । जब आप कॉल करते हैं free, तो आवंटनकर्ता केवल ओएस के लिए पृष्ठों को जारी कर सकता है यदि आप केवल freeमैपिंग में अंतिम लाइव ऑब्जेक्ट (या डेटा सेगमेंट के अंतिम एन पृष्ठों में) हो सकते हैं। ऐसा कोई तरीका नहीं है कि आपका एप्लिकेशन यथोचित भविष्यवाणी कर सके, या यह भी पता लगा सके कि यह पहले से हुआ था।

CPython इसे और भी जटिल बनाता है - इसमें कस्टम मेमोरी एलोकेटर के शीर्ष पर कस्टम 2-स्तरीय ऑब्जेक्ट एलोकेटर है malloc। ( अधिक विस्तृत विवरण के लिए स्रोत टिप्पणियां देखें ।) और उस के शीर्ष पर, यहां तक ​​कि सी एपीआई स्तर पर, बहुत कम पायथन, शीर्ष स्तर की वस्तुओं के डील-डौल होने पर आप सीधे नियंत्रण भी नहीं करते हैं।

इसलिए, जब आप एक ऑब्जेक्ट जारी करते हैं, तो आप कैसे जानते हैं कि क्या यह ओएस को मेमोरी जारी करने जा रहा है? ठीक है, पहले आपको यह जानना होगा कि आपने अंतिम संदर्भ जारी कर दिया है (किसी भी आंतरिक संदर्भ सहित, जिसके बारे में आपको पता नहीं था), जीसी को इससे निपटने की अनुमति देता है। (अन्य कार्यान्वयनों के विपरीत, कम से कम CPython किसी वस्तु को जितनी जल्दी हो सके अनुमति दे देगा।) यह आमतौर पर अगले स्तर पर कम से कम दो चीजों को निपटाता है (जैसे, एक स्ट्रिंग के लिए, आप PyStringऑब्जेक्ट जारी कर रहे हैं , और स्ट्रिंग बफर )।

यदि आप करते हैं एक वस्तु पुनःआवंटन, जानने के लिए कि इस वस्तु भंडारण के एक ब्लॉक पुनःआवंटन को अगले स्तर नीचे का कारण बनता है, तो आप वस्तु संभाजक की आंतरिक स्थिति पता करने के लिए, साथ ही साथ यह कैसे लागू हो जाता है के रूप में की है। (यह स्पष्ट रूप से तब तक नहीं हो सकता है जब तक कि आप ब्लॉक की आखिरी चीज़ को नहीं निपटा रहे हों, और तब भी, ऐसा नहीं हो सकता।)

यदि आप कर वस्तु भंडारण के एक ब्लॉक पुनःआवंटन, पता करने के लिए यह एक का कारण बनता है कि क्या freeकॉल, आप PyMem संभाजक की आंतरिक स्थिति पता करने के लिए, साथ ही साथ यह कैसे लागू हो जाता है के रूप में की है। (फिर से, आपको एक mallocएड क्षेत्र के अंतिम इन-उपयोग ब्लॉक को डील करना होगा, और तब भी, ऐसा नहीं हो सकता है।)

यदि आप ऐसा free एक mallocएड क्षेत्र, पता करने के लिए यह एक का कारण बनता है कि क्या munmapया समकक्ष (या brk), आप की आंतरिक स्थिति पता करने के लिए है malloc, यह कैसे लागू हो जाता है और साथ ही। और यह एक, दूसरों के विपरीत, अत्यधिक मंच-विशिष्ट है। (और फिर, आपको आम तौर पर mallocएक mmapसेगमेंट में अंतिम इन-डील का उपयोग करना होगा , और तब भी ऐसा नहीं हो सकता है।)

इसलिए, यदि आप यह समझना चाहते हैं कि यह 50.5mb के रिलीज के लिए क्यों हुआ, तो आपको इसे नीचे से ऊपर ट्रेस करना होगा। mallocजब आपने एक या एक से अधिक freeकॉल किए (तो शायद 50.5mb से अधिक के लिए) 50.5mb पृष्ठों को अनमैप क्यों किया ? आपको अपने प्लेटफ़ॉर्म को पढ़ना होगा malloc, और फिर इसकी वर्तमान स्थिति देखने के लिए विभिन्न तालिकाओं और सूचियों पर चलना होगा। (कुछ प्लेटफार्मों पर, यह सिस्टम-स्तर की जानकारी का उपयोग भी कर सकता है, जो ऑफ़लाइन निरीक्षण करने के लिए सिस्टम का स्नैपशॉट बनाए बिना कैप्चर करना बहुत असंभव है, लेकिन सौभाग्य से यह आमतौर पर एक समस्या नहीं है।) और फिर आपको यात्रा करना होगा। ऊपर के 3 स्तरों पर एक ही काम करें।

तो, प्रश्न का एकमात्र उपयोगी उत्तर "क्योंकि" है।

जब तक आप संसाधन-सीमित (जैसे, एम्बेडेड) विकास नहीं कर रहे हैं, आपके पास इन विवरणों की परवाह करने का कोई कारण नहीं है।

और यदि आप संसाधन-सीमित विकास कर रहे हैं, तो इन विवरणों को जानना बेकार है; आपको बहुत अधिक उन सभी स्तरों के आसपास एंड-रन करना होगा और विशेष रूप mmapसे मेमोरी की आवश्यकता आपको एप्लिकेशन स्तर पर होगी (संभवतः एक सरल, अच्छी तरह से समझी गई, बीच में आवेदन-विशिष्ट क्षेत्र आवंटनकर्ता)।


2

पहले, आप झलकियाँ स्थापित करना चाहते हैं:

sudo apt-get install python-pip build-essential python-dev lm-sensors 
sudo pip install psutil logutils bottle batinfo https://bitbucket.org/gleb_zhulik/py3sensors/get/tip.tar.gz zeroconf netifaces pymdstat influxdb elasticsearch potsdb statsd pystache docker-py pysnmp pika py-cpuinfo bernhard
sudo pip install glances

फिर इसे टर्मिनल में चलाएं!

glances

अपने पायथन कोड में, फ़ाइल की शुरुआत में जोड़ें, निम्नलिखित:

import os
import gc # Garbage Collector

"बिग" चर (उदाहरण के लिए: myBigVar) का उपयोग करने के बाद, जिसके लिए आप मेमोरी जारी करना चाहते हैं, अपने अजगर कोड में निम्नलिखित लिखें:

del myBigVar
gc.collect()

एक अन्य टर्मिनल में, अपने अजगर कोड को चलाएं और "झलक" टर्मिनल में देखें कि आपके सिस्टम में मेमोरी कैसे प्रबंधित की जाती है!

सौभाग्य!

PS मुझे लगता है कि आप एक डेबियन या उबंटू प्रणाली पर काम कर रहे हैं

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.