2013-04-05 15 views
8

Her biri 40000 parçalı liste içeren çok sayıda işlem var. Bu neredeyse makinede mevcut olan belleği maksimuma çıkarır. Bunu yaparsam:python verimliliği ve bellekte büyük nesneler

 while len(collection) > 0: 
      row = collection.pop(0) 
      row_count = row_count + 1 
      new_row = [] 
      for value in row: 
       if value is not None: 
        in_chars = str(value) 
       else: 
        in_chars = "" 

       #escape any naughty characters 
       new_row.append("".join(["\\" + c if c in redshift_escape_chars else c for c in in_chars])) 
      new_row = "\t".join(new_row) 
      rows += "\n"+new_row 
      if row_count % 5000 == 0: 
       gc.collect() 

bu ücretsiz belleği daha mu?

+1

Sinsi olmak istemem ama neden denemiyorsun? –

+0

, birisinin GC'nin genel yüküne ya da gözden kaçan bir python iç nesnesiyle genel değere değip değmeyeceğini biliyor olabilir. – tipu

+2

Mümkünse, tekrarlayıcıları kullanma ve 40k'lık tupları bir kerede işleme sokma, listeyi oluşturma ve aynı anda işleme koyma olasılığını dikkate almaya değer olabilir. Bu, bazı ek karmaşıklık olacak ve ilgili çabaya değmeyebilir. – Moshe

cevap

7

collection, rows'un aynı oranda küçülmesinden beri, bellek kullanımınız sabit kalacaktır.gc.collect() numaralı çağrı çok fazla fark yaratmayacak. CPython içinde

Bellek yönetimi ince olduğunu. Sadece referansları kaldırdığınız ve bir toplama döngüsü yürüttüğünüzden, belleğin işletim sistemine geri gönderileceği anlamına gelmez. Bakınız this answer for details.

gerçekten öğelerin büyük listelerinin yerine jeneratörler ve yineleyiciler etrafında bu kodu yapılandırmalıyım, hafızayı kaydetmek için. Bağlantı zaman aşımlarına sahip olduğunuzu söylediğinize çok şaşırdım çünkü tüm satırların getirilmesi bir anda bir satır getirmekten ve yaptığınız basit işlemi gerçekleştirmekten çok daha fazla zaman almamalıdır. Belki de db-getirme koduna bir göz atmalıyız?

satır-at-a-time işleme değil, bir olasılık, o zaman en azından bir iletmenin deque olarak verilerinizi korumak ve jeneratör ve adım adım elde üzerindeki tüm işlem gerçekleştirmek gerçekten ise.

Ben bu farklı yaklaşımları değineceğiz. Her şeyden

Birincisi, bazı ortak işlevler: Bu, mümkün olan en az miktarda alacak

cursor = db.cursor() 
cursor.execute("""SELECT * FROM bigtable""") 
rowstrings = (tabulate(row) for row in cursor.fetchall()) 
lines = istrjoin("\n", rowstrings) 
file_like_obj.writelines(lines) 
cursor.close() 

:

# if you don't need random-access to elements in a sequence 
# a deque uses less memory and has faster appends and deletes 
# from both the front and the back. 
from collections import deque 
from itertools import izip, repeat, islice, chain 
import re 

re_redshift_chars = re.compile(r'[abcdefg]') 

def istrjoin(sep, seq): 
    """Return a generator that acts like sep.join(seq), but lazily 

    The separator will be yielded separately 
    """ 
    return islice(chain.from_iterable(izip(repeat(sep), seq)), 1, None) 

def escape_redshift(s): 
    return re_redshift_chars.sub(r'\\\g<0>', s) 

def tabulate(row): 
    return "\t".join(escape_redshift(str(v)) if v is not None else '' for v in row) 

Şimdi idealdir böyle satır-at-a-time işleme vardır bellek - her seferinde sadece bir satır. Eğer gerçekten tüm resultset saklamak gerekiyorsa

, biraz kodunu değiştirebilirsiniz:

cursor = db.cursor() 
cursor.execute("SELECT * FROM bigtable") 
collection = deque(cursor.fetchall()) 
cursor.close() 
rowstrings = (tabulate(row) for row in collection) 
lines = istrjoin("\n", rowstrings) 
file_like_obj.writelines(lines) 

Şimdi tamamen Tüm programı çalıştırmak için hafızada kalır collection ilk haline Tüm sonuçları toplamak.

Ancak biz de kullanıldıkları olarak toplama öğeleri silme yaklaşımınızı çoğaltabilirsiniz. kaynak koleksiyonunu çalıştığı için boşaltan bir jeneratör oluşturarak aynı "kod şeklini" koruyabiliriz. Böyle bir şey olacaktır: Kullandığınız kadar belleği boşaltmak istediğinizde

def drain(coll): 
    """Return an iterable that deletes items from coll as it yields them. 

    coll must support `coll.pop(0)` or `del coll[0]`. A deque is recommended! 
    """ 
    if hasattr(coll, 'pop'): 
     def pop(coll): 
      try: 
       return coll.pop(0) 
      except IndexError: 
       raise StopIteration 
    else: 
     def pop(coll): 
      try: 
       item = coll[0] 
      except IndexError: 
       raise StopIteration 
      del coll[0] 
      return item 
    while True: 
     yield pop(coll) 

Şimdi kolaylıkla collection için drain(collection) yerine kullanabilirsiniz. drain(collection) tükendikten sonra collection nesnesi boş olacaktır.

2

Algoritmanız, listenin başına veya soldan açılırsa, daha hızlı bir alternatif olarak collections nesnesinden deque nesnesini kullanabilirsiniz. Bir karşılaştırma olarak

:

import timeit 

f1=''' 
q=deque() 
for i in range(40000): 
    q.append((i,i,'tuple {}'.format(i))) 

while q: 
    q.popleft() 
''' 

f2=''' 
l=[] 
for i in range(40000): 
    l.append((i,i,'tuple {}'.format(i))) 

while l: 
    l.pop(0) 
''' 

print 'deque took {:.2f} seconds to popleft()'.format(timeit.timeit(stmt=f1, setup='from collections import deque',number=100)) 
print 'list took {:.2f} seconds to pop(0)'.format(timeit.timeit(stmt=f2,number=100)) 

Baskılar:

deque took 3.46 seconds to to popleft() 
list took 37.37 seconds to pop(0) 

Yani liste veya kuyruğun başında deque itibaren haşhaş bu özel test için fazla 10x daha hızlıdır.

Bu büyük avantaj sadece sol taraf içindir. Eğer aynı testi pop() ile çalıştırırsanız her iki hız da kabaca aynıdır. Ayrıca listeyi yerinde tersine çevirebilir ve deque'den popleft ile aynı sonuçları elde etmek için sağ taraftan pop edebilirsiniz.


'Verimlilik' açısından, veritabanından tek satırları işlemek çok daha verimli olacaktır. Bu bir seçenek değilse, listenizi (veya koleksiyonunu) 'koleksiyon' yerine yerleştirin.

Bu satırlarda bir şey deneyin.

Birincisi, satır işleme patlak:

def cgen(collection): 
    # if collection is a deque: 
    while collection: 
     yield '\n'+process_row(collection.popleft()) 

Veya bir listeye sadık istiyorsanız: soldan hızlı pops izin vermek için bir deque kullanarak bakmak Şimdi

def process_row(row): 
    # I did not test this obviously, but I think I xlated your row processing faithfully 
    new_row = [] 
    for value in row: 
     if value: 
      in_chars = str(value)   
     else 
      in_char='' 
     new_row.append("".join(["\\" + c if c in redshift_escape_chars else c for c in in_chars])) 
    return '\t'.join(new_row)  

:

def cgen(collection): 
    collection.reverse() 
    while collection: 
     yield '\n'+process_row(collection.pop()) 

Ben orijinal pop (0), süreç satır yaklaşımı ve her 5000 satır pr gc çağrı düşünüyorum oburca suboptimal. Gc otomatik olarak daha sık çağrılacak.

Benim son öneri:

  1. bir deque kullanın. Sadece bir list gibi ama sol taraftaki itme veya pops için daha hızlı;
  2. popleft()'u kullanın; böylece listeyi tersine çevirmenize gerek yoktur (collection'un sırası anlamlıysa);
  3. Koleksiyonunuzu üretken olarak üretin;
  4. Sizin için hiçbir şey yapmadığı için gc aramayı düşünün. Sadece db'yi çağırıp 1 sırayı alabilir ve bir seferde 1 sırayı işleyebilirseniz 1-4'ü buraya atın!
+2

Aşağı seçmenin düşüncelerini biliyor olabilir miyim? – dawg