2013-04-05 35 views
8

tôi có nhiều quy trình xử lý danh sách có 40000 bộ dữ liệu. điều này gần như tối đa bộ nhớ có sẵn trên máy. nếu tôi thực hiện điều này:hiệu quả python và các đối tượng lớn trong bộ nhớ

 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() 

có thêm bộ nhớ này nữa không?

+1

Tôi không có nghĩa là để được snide nhưng tại sao bạn không thử nó và xem? –

+0

figured ai đó có thể biết cho dù đó là tổng thể giá trị nó với các chi phí của GC hoặc một số công cụ nội bộ python xảy ra mà tôi đã có thể bỏ qua. – tipu

+2

Nếu có thể, có thể đáng xem xét khả năng sử dụng các trình vòng lặp và thay vì xử lý tất cả các bộ dữ liệu 40k cùng một lúc, hãy xây dựng danh sách và xử lý chúng cùng một lúc. Điều đó sẽ có thêm một số phức tạp, và có thể không đáng để nỗ lực tham gia. – Moshe

Trả lời

7

Kể từ khi collection bị thu hẹp với cùng tốc độ mà rows đang tăng, mức sử dụng bộ nhớ của bạn sẽ ổn định.Cuộc gọi gc.collect() sẽ không tạo ra nhiều khác biệt.

Quản lý bộ nhớ trong CPython là tinh tế. Chỉ vì bạn loại bỏ các tham chiếu và chạy một chu trình thu thập không nhất thiết có nghĩa là bộ nhớ sẽ được trả lại cho hệ điều hành. Xem this answer for details.

Để thực sự lưu bộ nhớ, bạn nên cấu trúc mã này xung quanh các trình tạo và trình lặp thay vì danh sách các mục lớn. Tôi rất ngạc nhiên khi bạn nói rằng bạn đang có thời gian chờ kết nối vì việc tìm nạp tất cả các hàng sẽ không mất nhiều thời gian hơn tìm nạp hàng tại một thời điểm và thực hiện quá trình xử lý đơn giản mà bạn đang thực hiện. Có lẽ chúng ta nên xem xét mã tìm nạp db của bạn?

Nếu việc xử lý hàng-tại-một thời gian thực sự không phải là khả năng, thì ít nhất giữ dữ liệu của bạn như là một deque bất biến và thực hiện tất cả xử lý trên nó với máy phát và trình lặp.

Tôi sẽ phác thảo các phương pháp tiếp cận khác nhau này.

Trước hết, một số chức năng chung:

# 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) 

Bây giờ lý tưởng là hàng-at-a-thời gian xử lý, như thế này:

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() 

này sẽ đưa số tiền ít nhất có thể xảy ra bộ nhớ - chỉ một hàng tại một thời điểm.

Nếu bạn thực sự cần phải lưu trữ toàn bộ resultset, bạn có thể thay đổi mã hơi:

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) 

Bây giờ chúng tôi thu thập tất cả các kết quả vào collection đầu tiên mà vẫn hoàn toàn trong bộ nhớ cho toàn bộ chạy chương trình.

Tuy nhiên, chúng tôi cũng có thể sao chép cách tiếp cận của bạn trong việc xóa các mục bộ sưu tập khi chúng được sử dụng. Chúng tôi có thể giữ cùng một "hình dạng mã" bằng cách tạo một trình tạo mà làm trống bộ sưu tập nguồn của nó khi nó hoạt động. Nó sẽ giống như thế này:

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) 

Bây giờ bạn có thể dễ dàng thay thế drain(collection) cho collection khi bạn muốn giải phóng bộ nhớ khi bạn đi. Sau khi drain(collection) bị cạn kiệt, đối tượng collection sẽ trống.

2

Nếu thuật toán của bạn phụ thuộc vào pop'ing từ bên trái hoặc đầu danh sách, bạn có thể sử dụng đối tượng deque từ collections làm phương án thay thế nhanh hơn.

Là một so sánh:

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)) 

Prints:

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

Vì vậy, để thử nghiệm đặc biệt này của popping từ đầu danh sách hoặc hàng đợi, deque là hơn 10x nhanh hơn.

Lợi thế lớn này chỉ dành cho phía bên trái. Nếu bạn chạy thử nghiệm này cùng với pop() trên cả hai tốc độ là gần như nhau. Bạn cũng có thể đảo ngược danh sách tại chỗ và bật từ bên phải để có được kết quả tương tự như popleft từ deque.


Về mặt hiệu quả, sẽ hiệu quả hơn khi xử lý các hàng đơn lẻ từ cơ sở dữ liệu. Nếu đó không phải là một lựa chọn, hãy xử lý danh sách của bạn (hoặc deque) 'bộ sưu tập' tại chỗ.

Hãy thử điều gì đó dọc theo các dòng này.

Thứ nhất, thoát ra khỏi chế biến hàng:

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)  

Bây giờ nhìn vào cách sử dụng một deque cho phép pops nhanh từ trái:

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

Hoặc nếu bạn muốn dính vào một danh sách:

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

Tôi nghĩ rằng cách tiếp cận ban đầu của bạn về pop (0), xử lý hàng và gọi gc cứ 5000 hàng là pr obop suboptimal. Các gc sẽ được gọi tự động nhiều hơn thường xuyên hơn anyway.

khuyến nghị cuối cùng của tôi:

  1. Sử dụng một deque. Nó giống như một list nhưng nhanh hơn cho push bên trái hoặc bật;
  2. Sử dụng popleft() để bạn không cần phải đảo ngược danh sách (nếu thứ tự collection có ý nghĩa);
  3. Xử lý bộ sưu tập của bạn tại chỗ làm máy phát;
  4. Vứt bỏ khái niệm gọi gc vì nó không làm gì cho bạn.
  5. Ném ra 1-4 tại đây nếu bạn chỉ có thể gọi cho db và nhận 1 hàng và xử lý 1 hàng cùng một lúc!
+2

Tôi có thể biết về suy nghĩ của cử tri xuống không? – dawg

Các vấn đề liên quan