Python'da belleği serbest bırakma


128

Aşağıdaki örnekte bellek kullanımı ile ilgili birkaç sorum var.

  1. Tercümanda koşarsam,

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

    makinemde kullanılan gerçek hafıza kadar artıyor 80.9mb. Ben o zaman,

    del foo

    Gerçek bellek ama sadece, iner 30.4mb. Yorumlayıcı 4.4mbtemel kullanır , bu yüzden 26mbişletim sistemine bellek bırakmamanın avantajı nedir? Python "ileriyi planladığı" ve bu kadar belleği tekrar kullanabileceğinizi düşündüğü için mi?

  2. Neden 50.5mbözellikle serbest bırakılıyor - serbest bırakılan miktar neye dayanıyor?

  3. Python'u kullanılan tüm belleği serbest bırakmaya zorlamanın bir yolu var mı (bu kadar belleği tekrar kullanmayacağınızı biliyorsanız)?

NOT Bu soru, Python'da belleği nasıl açıkça boşaltabilirim? Sorusundan farklıdır. çünkü bu soru esas olarak, yorumlayıcı çöp toplama yoluyla nesneleri serbest bıraktıktan sonra bile (kullanımıyla gc.collectveya kullanmayarak) bellek kullanımının temelden artışıyla ilgilenir .


4
Bu davranışın Python'a özgü olmadığını belirtmek gerekir. Genellikle, bir işlem yığın ayrılan belleğin bir kısmını serbest bıraktığında, işlem ölünceye kadar belleğin işletim sistemine geri gönderilmemesi durumudur.
NPE

Sorunuz birden çok şey soruyor - bazıları çift, bazıları SO için uygun değil, bazıları iyi sorular olabilir. Python'un belleği serbest bırakıp bırakmadığını, tam olarak hangi koşullar altında yapıp yapamayacağını, altında yatan mekanizmanın ne olduğunu, neden bu şekilde tasarlandığını, herhangi bir geçici çözüm olup olmadığını veya tamamen başka bir şeyin olup olmadığını mı soruyorsunuz?
abarnert

2
@abarnert Benzer olan alt soruları birleştirdim. Sorularınıza yanıt vermek için: Python'un işletim sistemine biraz bellek bıraktığını biliyorum, ancak neden hepsini değil ve neden bu kadarını yapıyor? Olamayacağı durumlar varsa, neden? Ne geçici çözümler de var.
Jared


@jww Sanmıyorum. Bu soru gerçekten, çağrıları ile tamamen çöp topladıktan sonra bile yorumlayıcı işleminin neden belleği serbest bırakmadığıyla ilgilidir gc.collect.
Jared

Yanıtlar:


86

Yığın üzerine ayrılan bellek, yüksek su işaretlerine tabi olabilir. Bu, Python'un PyObject_Malloc4 KiB havuzunda küçük nesneleri ( ) tahsis etmek için dahili optimizasyonları ile karmaşıktır , tahsis boyutları için 8 bayt katları - 256 bayta kadar (3.3'te 512 bayt). Havuzların kendisi 256 KiB sahasındadır, bu nedenle bir havuzda sadece bir blok kullanılırsa, 256 KiB sahasının tamamı serbest bırakılmayacaktır. Python 3.3'te küçük nesne ayırıcı, yığın yerine anonim bellek haritalarını kullanmaya değiştirildi, bu nedenle belleği serbest bırakmada daha iyi performans göstermelidir.

Ek olarak, yerleşik türler, küçük nesne ayırıcıyı kullanabilen veya kullanmayan önceden ayrılmış nesnelerin serbest listelerini tutar. intTürünün kendi ayrılan bellek ile bir freelist tutar ve bunları temizlemek çağırarak gerektirir PyInt_ClearFreeList(). Bu, dolaylı olarak tam yaparak çağrılabilir gc.collect.

Böyle dene ve bana ne aldığını söyle. İşte psutil.Process.memory_info için bağlantı .

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)

Çıktı:

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

Düzenle:

Sistemdeki diğer işlemlerin etkilerini ortadan kaldırmak için işlem VM boyutuna göre ölçüme geçtim.

C çalışma zamanı (örneğin, glibc, msvcrt), üstteki bitişik boş alan sabit, dinamik veya yapılandırılabilir bir eşiğe ulaştığında yığını küçültür. Glibc ile bunu mallopt(M_TRIM_THRESHOLD) ile ayarlayabilirsiniz . Bu göz önüne alındığında, yığının sizden daha fazla, hatta çok daha fazla küçülmesi şaşırtıcı değildir free.

3.x'te rangeliste oluşturmaz, bu nedenle yukarıdaki test 10 milyon intnesne oluşturmaz . Öyle olsa bile, 3.x'teki inttür temelde longbir 2.x'tir ve bir serbest liste uygulamaz.


Yerine memory_info()kullanın get_memory_info()ve xtanımlanır
Aziz Alto

intPython 3'te bile 10 ^ 7 s elde edersiniz , ancak her biri döngü değişkeninde sonuncuyu değiştirir, böylece hepsi aynı anda
Davis Herring

Bir bellek sızıntısı sorunuyla karşılaştım ve sanırım burada yanıtlamanızın nedeni bu. Ama tahminimi nasıl kanıtlayabilirim? Birçok havuzun malloced olduğunu, ancak sadece küçük bir bloğun kullanıldığını gösteren herhangi bir araç var mı?
ruiruige1991

130

Sanırım burada gerçekten önemsediğiniz soru şudur:

Python'u kullanılan tüm belleği serbest bırakmaya zorlamanın bir yolu var mı (bu kadar belleği tekrar kullanmayacağınızı biliyorsanız)?

Hayır yok. Ancak kolay bir çözüm var: çocuk süreçler.

5 dakika boyunca 500 MB geçici depolamaya ihtiyacınız varsa, ancak bundan sonra 2 saat daha çalışmanız gerekiyorsa ve bir daha bu kadar belleğe dokunmayacaksınız, yoğun bellek gerektiren işi yapmak için bir alt işlem oluşturun. Çocuk süreci ortadan kalktığında, hafıza serbest kalır.

Bu tamamen önemsiz ve ücretsiz değildir, ancak oldukça kolay ve ucuzdur ve bu genellikle ticaretin değerli olması için yeterince iyidir.

İlk olarak, bir alt süreç oluşturmanın en kolay yolu şudur concurrent.futures(veya 3.1 ve öncesi için futuresPyPI'deki arka port):

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

Biraz daha kontrole ihtiyacınız varsa, multiprocessingmodülü kullanın .

Maliyetler:

  • Başta Windows olmak üzere bazı platformlarda işlem başlangıcı biraz yavaştır. Burada dakikalardan değil milisaniyelerden bahsediyoruz ve eğer bir çocuğu 300 saniyelik bir iş yapması için döndürüyorsanız, farkına bile varmayacaksınız. Ama bedava değil.
  • Kullandığınız büyük miktarda geçici bellek gerçekten büyükse , bunu yapmak ana programınızın değiştirilmesine neden olabilir. Elbette uzun vadede zamandan tasarruf ediyorsunuz, çünkü eğer bu bellek sonsuza kadar ortalıkta kalırsa bir noktada değişime yol açması gerekir. Ancak bu, bazı kullanım durumlarında kademeli yavaşlığı birden çok kez (ve erken) gecikmelere dönüştürebilir.
  • İşlemler arasında büyük miktarda veri göndermek yavaş olabilir. Yine, 2K'dan fazla argüman göndermekten ve 64K sonuç almaktan bahsediyorsanız, bunu fark etmeyeceksiniz, ancak büyük miktarda veri gönderip alıyorsanız, başka bir mekanizma kullanmak isteyeceksiniz. (bir dosya, mmapped veya başka bir şekilde; paylaşılan bellek API'leri multiprocessing; vb.).
  • İşlemler arasında büyük miktarda veri göndermek, verilerin toplanabilir olması gerektiği anlamına gelir (veya bunları bir dosyaya veya paylaşılan belleğe yapıştırırsanız, struct-able veya ideal olarak ctypes-able).

Problemi
çözmese de

32

eryksun 1. soruyu cevapladı ve ben 3. soruyu (orijinal 4. soru) cevapladım, ama şimdi 2. soruyu cevaplayalım:

Neden özellikle 50.5mb yayınlıyor - serbest bırakılan miktar neye dayanıyor?

Nihayetinde dayandığı şey, Python içindeki ve malloctahmin edilmesi çok zor olan bir dizi tesadüf .

Birincisi, belleği nasıl ölçtüğünüze bağlı olarak, yalnızca gerçekten belleğe eşlenmiş sayfaları ölçüyor olabilirsiniz. Bu durumda, bir sayfa çağrı cihazı tarafından her değiştirildiğinde, bellek serbest bırakılmamış olsa bile "serbest" olarak görünecektir.

Veya tahsis edilmiş ama hiç dokunulmamış sayfaları (linux gibi iyimser bir şekilde fazla tahsis eden sistemlerde), tahsis edilmiş ancak etiketlenmiş sayfaları MADV_FREEvb. Sayan veya saymayan kullanımdaki sayfaları ölçüyor olabilirsiniz .

Tahsis edilmiş sayfaları gerçekten ölçüyorsanız (ki bu aslında yapmak için çok yararlı bir şey değil, ama sorduğunuz şey gibi görünüyor) ve sayfaların tahsisi gerçekten kaldırılmışsa, bunun olabileceği iki koşul: Ya siz ' brkveri segmentini küçültmek için kullandınız veya eşdeğeri (günümüzde çok nadirdir) veya munmapeşlenmiş bir segmenti serbest bırakmak için kullandınız veya benzer. (Ayrıca teorik olarak ikincisinin küçük bir varyantı vardır, çünkü eşlenmiş bir segmentin bir kısmını serbest bırakmanın yolları vardır - örneğin, eşlemesini hemen kaldırdığınız MAP_FIXEDbir MADV_FREEsegment için onu çalın.)

Ancak çoğu program bir şeyleri doğrudan bellek sayfalarından ayırmaz; Onlar kullanmak malloctarzı ayırıcı. Aradığınızda free, ayırıcı, yalnızca freebir eşlemede (veya veri segmentinin son N sayfasında) son canlı nesne olursanız, sayfaları işletim sistemine bırakabilir . Uygulamanızın bunu mantıklı bir şekilde tahmin etmesi veya önceden olduğunu tespit etmesi mümkün değildir.

CPython bunu daha da karmaşık hale getirir - üzerinde özel bir bellek ayırıcının üzerinde özel 2 seviyeli bir nesne ayırıcıya sahiptir malloc. ( Daha ayrıntılı bir açıklama için kaynak yorumlarına bakın .) Üstelik C API düzeyinde bile Python çok daha az, en üst düzey nesnelerin ayrıldıklarında doğrudan kontrol bile edemezsiniz.

Öyleyse, bir nesneyi serbest bıraktığınızda, işletim sisteminin belleği serbest bırakıp bırakmayacağını nasıl anlarsınız? Öncelikle, son referansı (bilmediğiniz dahili referanslar dahil) yayınladığınızı ve GC'nin onu serbest bırakmasına izin verdiğinizi bilmelisiniz. (Diğer uygulamalardan farklı olarak, en azından CPython bir nesneyi izin verildiği anda serbest bırakacaktır.) Bu genellikle bir sonraki seviyedeki en az iki şeyi serbest bırakır (örneğin, bir dizge için, PyStringnesneyi ve dize arabelleğini serbest bırakıyorsunuz) ).

Eğer varsa yapmak bu nesne depolama bloğunu ayırması sonraki seviye aşağı neden olmadığını bilmek, bir nesneyi ayırması, sen nasıl uygulandığı gibi, nesne ayırıcı iç durumunu bilmek zorunda. (Bloktaki son şeyi serbest bırakmadığınız sürece bu kesinlikle gerçekleşmez ve o zaman bile gerçekleşmeyebilir.)

Eğer varsa yapmak bu bir neden olmadığını bilmek, nesne depolama bloğunu ayırması freeçağrı, sen nasıl uygulandığı yanı sıra PyMem ayırıcı iç durumunu bilmek zorunda. (Yine, bir malloced bölgede son kullanımdaki bloğu kaldırmanız gerekir ve o zaman bile gerçekleşmeyebilir.)

Eğer varsa yapmak free bir malloced bölgesini, bu bir sebep olmadığını bilmek munmapveya eşdeğeri (veya brk, sen iç durumu bilmek zorunda) mallocnasıl uygulandığı yanı sıra. Ve bu, diğerlerinin aksine, oldukça platforma özgüdür. (Ve yine, genellikle mallocbir mmapsegmentte son kullanımda olanı kaldırmanız gerekir ve o zaman bile gerçekleşmeyebilir.)

Bu yüzden, neden tam olarak 50.5 MB'yi serbest bıraktığını anlamak istiyorsanız, onu aşağıdan yukarıya doğru izlemeniz gerekecek. mallocBu bir veya daha fazla freearamayı yaptığınızda neden 50.5 MB değerinde sayfanın haritasını çıkardınız (muhtemelen 50.5 MB'den biraz fazla)? Platformunuzu okumanız mallocve ardından mevcut durumunu görmek için çeşitli tabloları ve listeleri incelemeniz gerekir. (Bazı platformlarda, çevrimdışıyken incelemek için sistemin anlık görüntüsünü almadan yakalanması neredeyse imkansız olan sistem düzeyinde bilgileri bile kullanabilir, ancak neyse ki bu genellikle bir sorun değildir.) Ve sonra yapmanız gerekir. bunun üzerindeki 3 seviyede de aynı şeyi yapın.

Bu nedenle, sorunun tek yararlı cevabı "Çünkü" dir.

Kaynakla sınırlı (ör. Yerleşik) geliştirme yapmadığınız sürece, bu ayrıntılarla ilgilenmek için hiçbir nedeniniz yoktur.

Eğer olursa edilir kaynakları sınırlı geliştirme yaparak, bu ayrıntıları bilmeden işe yaramaz; hemen hemen tüm bu seviyelerde ve özellikle mmapuygulama seviyesinde ihtiyaç duyduğunuz belleği (muhtemelen arada basit, iyi anlaşılmış, uygulamaya özel bir bölge ayırıcı ile) etrafında bir son çalışma yapmanız gerekir.


2

İlk olarak, bir bakış yüklemek isteyebilirsiniz:

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

Ardından terminalde çalıştırın!

glances

Python kodunuzda, dosyanın başlangıcına aşağıdakileri ekleyin:

import os
import gc # Garbage Collector

Belleği serbest bırakmak istediğiniz "Büyük" değişkeni (örneğin: myBigVar) kullandıktan sonra, python kodunuzu aşağıdakileri yazın:

del myBigVar
gc.collect()

Başka bir terminalde, python kodunuzu çalıştırın ve sisteminizde belleğin nasıl yönetildiğini "bakışlar" terminalinde gözlemleyin!

İyi şanslar!

Not: Debian veya Ubuntu sistemi üzerinde çalıştığınızı varsayıyorum

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.