2014-11-20 14 views
5

Tôi sử dụng tập lệnh Python đơn giản sau đây để nén một tệp văn bản lớn (ví dụ: 10GB) trên một ví dụ m3. Tuy nhiên, tôi luôn có một MemoryError:Python - Cách gzip một tệp văn bản lớn không có MemoryError?

import gzip 

with open('test_large.csv', 'rb') as f_in: 
    with gzip.open('test_out.csv.gz', 'wb') as f_out: 
     f_out.writelines(f_in) 
     # or the following: 
     # for line in f_in: 
     #  f_out.write(line) 

Các traceback tôi nhận được là:

Traceback (most recent call last): 
    File "test.py", line 8, in <module> 
    f_out.writelines(f_in) 
MemoryError 

Tôi đã đọc một số cuộc thảo luận về vấn đề này, nhưng vẫn không khá rõ ràng làm thế nào để xử lý việc này. Ai đó có thể cho tôi một câu trả lời dễ hiểu hơn về cách đối phó với vấn đề này?

+0

Lỗi chính xác với giải pháp của Mark là gì? Nó không thể nằm trên 'f_out.writelines', vì bạn sử dụng' write' ... –

+0

Lỗi sẽ như sau: 'Traceback (cuộc gọi gần đây nhất): File" test.py ", dòng 8, trong cho dòng trong f_in: MemoryError' – shihpeng

Trả lời

5

Thật lạ lùng. Tôi mong đợi lỗi này nếu bạn cố gắng nén một tệp nhị phân lớn không chứa nhiều dòng mới, vì một tệp như vậy có thể chứa một "dòng" quá lớn đối với RAM của bạn, nhưng nó không nên xảy ra trên một dòng tệp .csv được cấu trúc.

Nhưng dù sao, nó không phải là rất hiệu quả để nén tệp theo từng dòng. Mặc dù hệ điều hành đệm đĩa I/O thường là nhiều hơn nhanh hơn để đọc và ghi các khối dữ liệu lớn hơn, ví dụ: 64 kB.

Tôi có 2GB bộ nhớ RAM trên máy này và tôi đã sử dụng thành công chương trình dưới đây để nén bản lưu trữ tar 2.8GB.

#! /usr/bin/env python 

import gzip 
import sys 

blocksize = 1 << 16  #64kB 

def gzipfile(iname, oname, level): 
    with open(iname, 'rb') as f_in: 
     f_out = gzip.open(oname, 'wb', level) 
     while True: 
      block = f_in.read(blocksize) 
      if block == '': 
       break 
      f_out.write(block) 
     f_out.close() 
    return 


def main(): 
    if len(sys.argv) < 3: 
     print "gzip compress in_file to out_file" 
     print "Usage:\n%s in_file out_file [compression_level]" % sys.argv[0] 
     exit(1) 

    iname = sys.argv[1] 
    oname = sys.argv[2] 
    level = int(sys.argv[3]) if len(sys.argv) > 3 else 6 

    gzipfile(iname, oname, level) 


if __name__ == '__main__': 
    main() 

Tôi đang chạy Python 2.6.6 và gzip.open() không hỗ trợ with.


Như Andrew Bay lưu ý trong các ý kiến, if block == '': sẽ không hoạt động chính xác trong Python 3, vì block chứa byte, không phải là một chuỗi, và một đối tượng byte rỗng không so sánh như tương đương với một chuỗi văn bản trống . Chúng tôi có thể kiểm tra chiều dài khối hoặc so sánh với b'' (cũng sẽ hoạt động trong Python 2.6+), nhưng cách đơn giản là if not block:.

+0

Cảm ơn câu trả lời của bạn, rõ ràng và hoạt động rất tốt :) – shihpeng

+0

Như một lưu ý bổ sung, tệp tôi đã sử dụng để kiểm tra chức năng gzip Python được tạo bởi 'fallocate -l 10G bigfile_file'. Python không thể gzip các tập tin lớn như vậy bởi các tập tin iteratable (nó có vẻ là một lỗi từ thời gian dài trước đây?). – shihpeng

+1

@shihpeng: Tôi không quen thuộc với 'fallocate', vì vậy đây chỉ là phỏng đoán, nhưng có thể gzip của Python không thích các tệp như vậy bởi vì chúng không chứa bất kỳ dữ liệu thực tế nào. Tôi không thể kiểm tra nó kể từ khi tôi vẫn đang sử dụng ext3 trên hệ thống này, mà không hỗ trợ 'fallocate'. Tuy nhiên, chương trình của tôi hoạt động tốt bằng cách sử dụng một tệp lớn được tạo bằng cách sử dụng 'truncate', tạo tệp thưa thớt. –

3

Thật lạ khi gặp lỗi bộ nhớ ngay cả khi đọc từng dòng tệp. Tôi cho rằng đó là vì bạn có rất ít bộ nhớ có sẵn và các dòng rất lớn. Sau đó, bạn nên sử dụng số lần đọc nhị phân:

import gzip 

#adapt size value : small values will take more time, high value could cause memory errors 
size = 8096 

with open('test_large.csv', 'rb') as f_in: 
    with gzip.open('test_out.csv.gz', 'wb') as f_out: 
     while True: 
      data = f_in.read(size) 
      if data == '' : break 
      f_out.write(data) 
+0

Có, m3.large chỉ có 2 bộ nhớ vcpu và 7gb, rất hạn chế nếu có một số quy trình hoặc máy chủ khác chạy trên cùng một cá thể. – shihpeng

+0

Điều này chỉ sao chép 8KB đầu tiên. – abarnert

+0

@abarnert Bạn nói đúng! Cố định ... –

6

Vấn đề ở đây không có gì để làm với gzip, và tất cả mọi thứ để làm với dòng đọc bởi dòng từ một tập tin 10GB không có dòng mới trong đó:

Là một lưu ý bổ sung, các tập tin tôi đã sử dụng để kiểm tra chức năng gzip Python được tạo ra bởi fallocate -l 10G bigfile_file.

Điều đó cung cấp cho bạn tệp có kích thước 10GB được tạo hoàn toàn bằng 0 byte. Có nghĩa là không có byte dòng mới. Có nghĩa là dòng đầu tiên dài 10GB. Có nghĩa là nó sẽ mất 10GB để đọc dòng đầu tiên. (Hoặc thậm chí có thể là 20 hoặc 40GB, nếu bạn đang sử dụng Python trước 3.3 và cố gắng đọc nó dưới dạng Unicode.)

Nếu bạn muốn sao chép dữ liệu nhị phân, đừng sao chép từng dòng. Cho dù đó là một tập tin bình thường, một GzipFile đó là giải nén cho bạn trên bay, một socket.makefile(), hoặc bất cứ điều gì khác, bạn sẽ có cùng một vấn đề.

Giải pháp là sao chép từng đoạn một.Hoặc chỉ sử dụng copyfileobj, tự động thực hiện điều đó cho bạn.

import gzip 
import shutil 

with open('test_large.csv', 'rb') as f_in: 
    with gzip.open('test_out.csv.gz', 'wb') as f_out: 
     shutil.copyfileobj(f_in, f_out) 

Theo mặc định, copyfileobj sử dụng một kích thước đoạn tối ưu hóa để được thường rất tốt và không bao giờ rất xấu. Trong trường hợp này, bạn có thể thực sự muốn có kích thước nhỏ hơn hoặc kích thước lớn hơn; thật khó để đoán trước được ưu tiên nào. * Vì vậy, hãy thử nghiệm nó bằng cách sử dụng timeit với các đối số bufsize khác nhau (giả sử, quyền hạn 4 từ 1KB đến 8MB) đến copyfileobj. Tuy nhiên, 16KB mặc định có thể sẽ đủ tốt trừ khi bạn đang làm rất nhiều điều này.

* Nếu kích thước bộ đệm quá lớn, bạn có thể kết thúc luân phiên các đoạn dài của I/O và các đoạn xử lý dài. Nếu nó quá nhỏ, bạn có thể cần nhiều lần đọc để điền vào một khối gzip duy nhất.

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