2011-01-30 6 views
6

Tôi đang viết tập lệnh nhập để xử lý tệp có khả năng hàng trăm nghìn dòng (tệp nhật ký). Sử dụng một cách tiếp cận rất đơn giản (dưới đây) đã mất đủ thời gian và bộ nhớ mà tôi cảm thấy như nó sẽ đưa ra MBP của tôi bất cứ lúc nào, vì vậy tôi đã giết chết quá trình.Cách phân tích cú pháp hiệu quả các tệp văn bản lớn trong Ruby

#... 
File.open(file, 'r') do |f| 
    f.each_line do |line| 
    # do stuff here to line 
    end 
end 

tập tin đặc biệt này có 642.868 dòng:

$ wc -l nginx.log                                  /code/src/myimport 
    642868 ../nginx.log 

Có ai biết của một (bộ nhớ/CPU) cách hiệu quả hơn để xử lý mỗi dòng trong tập tin này?

CẬP NHẬT

Mã bên trong f.each_line từ trên chỉ đơn giản là phù hợp với một regex chống lại dòng. Nếu không khớp, tôi thêm dòng vào một mảng @skipped. Nếu nó trôi qua, tôi định dạng các kết quả phù hợp thành một băm (được khóa bởi "trường" của trận đấu) và nối nó vào một mảng @results.

# regex built in `def initialize` (not on each line iteration) 
@regex = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - (.{0})- \[([^\]]+?)\] "(GET|POST|PUT|DELETE) ([^\s]+?) (HTTP\/1\.1)" (\d+) (\d+) "-" "(.*)"/ 

#... loop lines 
match = line.match(@regex) 
if match.nil? 
    @skipped << line 
else 
    @results << convert_to_hash(match) 
end 

Tôi hoàn toàn cởi mở với điều này là một quá trình không hiệu quả. Tôi có thể làm cho mã bên trong của convert_to_hash sử dụng một lambda precomputed thay vì tìm ra các tính toán mỗi lần. Tôi đoán tôi chỉ giả định nó là bản lặp dòng chính nó là vấn đề, không phải là mã mỗi dòng.

+0

Cách hiệu quả nhất về bộ nhớ là cách bạn đang thực hiện với 'each_line'. Bạn có thể đọc tệp trong các khối nhanh hơn, sau đó sử dụng 'Chuỗi # dòng' để lấy các dòng riêng lẻ cùng với nối lại bất kỳ dòng được tải một phần nào vượt qua các ranh giới khối. Nó trở thành một rửa phải tách ra các dòng và tái tham gia những người bị hỏng. –

Trả lời

5

Tôi vừa thực hiện kiểm tra trên một tệp 600.000 dòng và nó lặp lại trong tệp trong chưa đầy nửa giây. Tôi đoán sự chậm chạp không phải là trong các tập tin looping nhưng dòng phân tích cú pháp. Bạn có thể dán mã phân tích cú pháp của mình không?

+0

Đoạn mã duy nhất có bất kỳ ý nghĩa nào là tôi khớp đường thẳng với một regex bán phức tạp. Các regex không làm bất kỳ nhìn về phía trước/phía trước, nó chủ yếu chỉ là một trận đấu char-by-char. Tôi sẽ đăng một bản cập nhật ở trên với mã có liên quan. – localshred

+0

Ồ, và regex được tính một lần, không phải trên mỗi lần lặp (chỉ để rõ ràng). – localshred

+0

Dường như đó là sự ngu ngốc của tôi đã gây ra sự phát triển trí nhớ. Tôi đã lưu trữ các kết quả phù hợp (và cả các dòng bị bỏ qua) trong các mảng mà tôi đã sử dụng để thực hiện chèn db sau này (hoặc in kích thước bỏ qua). Tôi biết, tôi câm.:) Bây giờ tôi chỉ đang thực hiện 'puts' trên các dòng bị bỏ qua và thực hiện lệnh chèn db ngay khi đối sánh hợp lệ. Các mem thực sự không bao giờ đi trên 30mb. Cảm ơn vì đã chỉ ra rằng tôi có lẽ chỉ làm mọi thứ một cách câm lặng. :) (Oh và tôi chuyển sang 'IO.foreach' như câu trả lời ban đầu của bạn được đề xuất). – localshred

1

Nếu bạn đang sử dụng bash (hoặc tương tự), bạn có thể có thể để tối ưu hóa như thế này:

Trong input.rb:

while x = gets 
     # Parse 
end 

sau đó trong bash:

cat nginx.log | ruby -n input.rb 

Các -n gắn cờ cho ruby ​​là assume 'while gets(); ... end' loop around your script, điều này có thể khiến nó làm điều gì đó đặc biệt để tối ưu hóa.

Bạn cũng có thể muốn xem xét giải pháp được viết trước cho sự cố, vì điều đó sẽ nhanh hơn.

+0

Có vẻ hơi khó hiểu hơn tôi muốn vào thời điểm này, nhưng tôi sẽ ghi nhớ điều đó. – localshred

4

Điều này blogpost bao gồm một số phương pháp phân tích cú pháp tệp nhật ký lớn. Có lẽ đó là một nguồn cảm hứng. Ngoài ra, hãy xem file-tail gem