2009-05-11 27 views
8

Tôi phải xử lý các tệp văn bản thuần túy rất lớn (trên 10 gigabyte, vâng tôi biết nó phụ thuộc vào những gì chúng ta nên gọi lớn), với các dòng rất dài.sed tối ưu hóa (chỉnh sửa tập tin lớn dựa trên số liệu nhỏ hơn)

Nhiệm vụ gần đây nhất của tôi liên quan đến một số chỉnh sửa dòng dựa trên dữ liệu từ một tệp khác.

Tệp dữ liệu (cần được sửa đổi) chứa 1500000 dòng, mỗi dòng trong số đó là ví dụ: 800 ký tự dài. Mỗi dòng là duy nhất và chỉ chứa một số nhận dạng, mỗi số nhận dạng là duy nhất)

Tệp bổ trợ là ví dụ: 1800 dòng dài, chứa số nhận dạng và số lượng và ngày cần được sửa đổi trong tệp dữ liệu.

Tôi vừa chuyển đổi (với Vim regex) tệp sửa đổi thành sed, nhưng nó rất không hiệu quả.

Hãy nói rằng tôi có một dòng như thế này trong các tập tin dữ liệu:

(some 500 character)id_number(some 300 character) 

Và tôi cần phải sửa đổi dữ liệu trong phần 300 char.

Dựa trên các tập tin sửa đổi, tôi đưa ra dòng sed như thế này:

/id_number/ s/^\(.\{650\}\).\{20\}/\1CHANGED_AMOUNT_AND_DATA/ 

Vì vậy, tôi có 1800 dòng như thế này.

Nhưng tôi biết, mà ngay cả trên máy chủ rất nhanh, nếu tôi làm một

sed -i.bak -f modifier.sed data.file 

Nó rất chậm, bởi vì nó có để đọc tất cả các mẫu x mỗi dòng.

Không có cách nào tốt hơn?

Lưu ý: Tôi không phải là lập trình viên, chưa bao giờ học (ở trường) về thuật toán. Tôi có thể sử dụng awk, sed, một phiên bản lỗi thời của perl trên máy chủ.

+1

phiên bản cho perl là gì? –

+0

perl 5.8.6 i586-linux-thread-multi –

+1

Đó là * một phiên bản lỗi thời perl, nhưng tôi nghi ngờ không tệ như mọi người đã kết luận từ ghi chú của bạn;) – user55400

Trả lời

6

tôi cách tiếp cận (theo thứ tự khều) đề nghị sẽ được xử lý dữ liệu này như:

  1. Một cơ sở dữ liệu (thậm chí là một SQLite đơn giản dựa trên DB với một chỉ số sẽ thực hiện tốt hơn nhiều so với sed/awk trên một tập tin 10GB)
  2. một tập tin phẳng chứa kỷ lục cố định độ dài
  3. một tập tin phẳng chứa kỷ lục biến độ dài

Sử dụng một cơ sở dữ liệu mất c là tất cả những chi tiết nhỏ làm chậm quá trình xử lý tệp văn bản (tìm bản ghi bạn quan tâm, sửa đổi dữ liệu, lưu trữ nó trở lại DB). Hãy xem DBD :: SQLite trong trường hợp của Perl.

Nếu bạn muốn gắn bó với tệp phẳng, bạn sẽ muốn duy trì chỉ mục theo cách thủ công cùng với tệp lớn để bạn có thể dễ dàng tra cứu số bản ghi bạn cần thao tác hơn. Hoặc, tốt hơn, có lẽ số ID của bạn số bản ghi của bạn?

Nếu bạn có độ dài bản ghi thay đổi, tôi khuyên bạn nên chuyển sang độ dài bản ghi cố định (vì chỉ xuất hiện ID của bạn có độ dài thay đổi). Nếu bạn không thể làm điều đó, có lẽ bất kỳ dữ liệu hiện có sẽ không bao giờ di chuyển xung quanh trong tập tin? Sau đó, bạn có thể duy trì chỉ mục đã đề cập trước đó và thêm các mục mới nếu cần, với sự khác biệt là thay vì chỉ mục trỏ đến số bản ghi, bây giờ bạn trỏ đến vị trí tuyệt đối trong tệp.

+0

Tôi sẽ sử dụng phương pháp DB. Oracle có sẵn. Hiện tại sqlldr-ing ... –

+1

Giải pháp DB (với sqlldr, sqlplus) vừa kết thúc, trong khi sed vẫn chạy ở mức 7% ... –

3

Tôi đề nghị bạn viết một chương trình bằng Perl (vì tôi không phải là một chuyên gia sed/awk và tôi không có khả năng chính xác).

Bạn "thuật toán" rất đơn giản: bạn cần phải xây dựng, trước hết, một hashmap có thể cung cấp cho bạn chuỗi dữ liệu mới để áp dụng cho từng ID. Điều này là đạt được đọc tập tin sửa đổi của khóa học.

Khi bản đồ này được điền, bạn có thể duyệt từng dòng tệp dữ liệu của mình, đọc ID ở giữa dòng và tạo dòng mới như bạn đã mô tả ở trên.

Tôi cũng không phải là một chuyên gia Perl, nhưng tôi nghĩ rằng chương trình khá đơn giản.Nếu bạn cần sự giúp đỡ để viết nó, yêu cầu nó :-)

+0

Âm thanh như một giải pháp tốt, cung cấp ID của một dòng có thể được trích xuất với nỗ lực hợp lý - đó không phải là rõ ràng từ câu hỏi mà là một giả định tốt, imho. – user55400

2

Với perl, bạn nên sử dụng đế để lấy số id, đặc biệt nếu id_number có chiều rộng không đổi.

my $id_number=substr($str, 500, id_number_length); 

Sau đó nếu $ id_number nằm trong phạm vi, bạn nên sử dụng chất nền để thay thế văn bản còn lại.

substr($str, -300,300, $new_text); 

Cụm từ thông dụng của Perl rất nhanh, nhưng không phải trong trường hợp này.

0

Bạn gần như chắc chắn nên sử dụng cơ sở dữ liệu, như MikeyB suggested.

Nếu bạn không muốn sử dụng cơ sở dữ liệu vì lý do nào đó, nếu danh sách các sửa đổi sẽ khớp với bộ nhớ (hiện tại là 1800 dòng), phương pháp hiệu quả nhất là một hashtable được bổ sung được đề xuất bởi yves Baumes.

Nếu bạn nhận được đến điểm mà ngay cả những danh sách các thay đổi trở nên rất lớn, bạn cần phải sắp xếp cả các file bằng ID của họ và sau đó thực hiện một danh sách merge - về cơ bản:

  1. Hãy so sánh ID tại "top" của tập tin đầu vào với ID tại "top" của những sửa đổi tập tin
  2. Điều chỉnh các hồ sơ phù hợp nếu chúng phù hợp
  3. Viết nó ra
  4. Huỷ "top" dòng từ bất cứ tập tin có (theo thứ tự bảng chữ cái hoặc là số ID thấp nhất và đọc một dòng khác từ tệp đó
  5. Goto 1.

Phía sau hậu trường, cơ sở dữ liệu gần như chắc chắn sẽ sử dụng danh sách hợp nhất nếu bạn thực hiện thay đổi này bằng một lệnh SQL UPDATE.

0

Thỏa thuận tốt về quyết định sqlloader hoặc datadump. Đó là con đường để đi.

+0

Điều này cần phải được đăng làm bình luận. – Viet

1

Đề xuất của tôi là, không sử dụng cơ sở dữ liệu. Kịch bản perl được viết tốt sẽ làm tốt hơn cơ sở dữ liệu theo thứ tự độ lớn trong loại tác vụ này. Tôi tin tưởng, tôi có nhiều kinh nghiệm thực tế với nó. Bạn sẽ không nhập dữ liệu vào cơ sở dữ liệu khi perl sẽ được hoàn thành.

Khi bạn viết 1500000 dòng với 800 ký tự, có vẻ như 1,2 GB cho tôi. Nếu bạn sẽ có đĩa rất chậm (30MB/s), bạn sẽ đọc nó trong 40 giây. Với tốt hơn 50 -> 24, 100 -> 12s và như vậy. Nhưng perl hash tra cứu (như db tham gia) tốc độ trên CPU 2GHz là trên 5Mlookups/s. Nó có nghĩa là công việc CPU của bạn bị ràng buộc sẽ là trong vài giây và bạn làm việc IO ràng buộc sẽ được trong hàng chục giây. Nếu nó thực sự là 10GB số sẽ thay đổi nhưng tỷ lệ là như nhau.

Bạn chưa xác định nếu sửa đổi dữ liệu thay đổi kích thước hay không (nếu sửa đổi có thể được thực hiện tại chỗ), do đó chúng tôi sẽ không giả định và sẽ hoạt động như bộ lọc. Bạn chưa chỉ định định dạng của "tệp sửa đổi" của bạn và loại sửa đổi nào. Giả sử rằng nó được tách ra bởi một cái gì đó tab như:

<id><tab><position_after_id><tab><amount><tab><data> 

Chúng tôi sẽ đọc dữ liệu từ thiết bị nhập chuẩn và ghi vào stdout và kịch bản có thể được một cái gì đó như thế này:

my $modifier_filename = 'modifier_file.txt'; 

open my $mf, '<', $modifier_filename or die "Can't open '$modifier_filename': $!"; 
my %modifications; 
while (<$mf>) { 
    chomp; 
    my ($id, $position, $amount, $data) = split /\t/; 
    $modifications{$id} = [$position, $amount, $data]; 
} 
close $mf; 

# make matching regexp (use quotemeta to prevent regexp meaningful characters) 
my $id_regexp = join '|', map quotemeta, keys %modifications; 
$id_regexp = qr/($id_regexp)/;  # compile regexp 

while (<>) { 
    next unless m/$id_regexp/; 
    next unless $modifications{$1}; 
    my ($position, $amount, $data) = @{$modifications{$1}}; 
    substr $_, $+[1] + $position, $amount, $data; 
} 
continue { print } 

Mở máy tính xách tay của tôi nó mất khoảng nửa phút cho 1,5 triệu hàng, 1800 mã tra cứu, dữ liệu 1.2 GB. Đối với 10GB, không được quá 5 phút. Là nó hợp lý nhanh chóng cho bạn?

Nếu bạn bắt đầu nghĩ rằng bạn đang không IO bị ràng buộc (ví dụ nếu sử dụng một số NAS) nhưng CPU bị ràng buộc, bạn có thể hy sinh một số khả năng đọc và thay đổi như thế này:

my $mod; 
while (<>) { 
    next unless m/$id_regexp/; 
    $mod = $modifications{$1}; 
    next unless $mod; 
    substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2]; 
} 
continue { print } 
+0

Mặc dù nhiệm vụ của tôi đã hoàn thành, tôi cũng sẽ thử lại giải pháp của bạn, vì Oracle không phải lúc nào cũng sẵn có. Dù sao, cảm ơn sự giúp đỡ của bạn. –

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