2009-08-09 17 views
5

Tôi hiểu rằng cả Java và Perl đều cố gắng tìm một kích thước phù hợp với kích thước bộ đệm mặc định khi đọc trong các tệp, nhưng tôi thấy sự lựa chọn của họ ngày càng lỗi thời và đang gặp sự cố khi thay đổi lựa chọn mặc định đến với Perl. Trong trường hợp Perl, tôi tin rằng sử dụng bộ đệm 8K theo mặc định, tương tự như sự lựa chọn của Java, tôi không thể tìm thấy tham chiếu bằng công cụ tìm kiếm trang web perldoc (thực sự là Google) về cách tăng bộ đệm đầu vào tệp mặc định kích thước để nói, 64K.Làm thế nào tôi có thể đặt kích thước bộ đệm đọc tệp trong Perl để tối ưu hóa nó cho các tệp lớn?

Từ liên kết ở trên, để hiển thị như thế nào 8K bộ đệm không mở rộng:

Nếu dòng thường có khoảng 60 ký tự mỗi, sau đó tập tin 10.000 dòng có khoảng 610.000 nhân vật trong đó. Đọc tập tin theo từng dòng với bộ đệm chỉ yêu cầu 75 cuộc gọi hệ thống và 75 lần chờ cho đĩa thay vì 10.001.

Vì vậy, đối với tệp 50.000.000 dòng với 60 ký tự trên mỗi dòng (bao gồm dòng mới ở cuối), với bộ đệm 8K, nó sẽ thực hiện 366211 cuộc gọi hệ thống để đọc tệp 2,8GiB. Như một sang một bên, bạn có thể xác nhận hành vi này bằng cách nhìn vào đĩa i/o đọc delta (trong Windows ít nhất, đầu trong * nix cho thấy điều tương tự bằng cách nào đó tôi cũng chắc chắn) trong danh sách quá trình quản lý tác vụ như chương trình Perl của bạn mất 10 phút để đọc trong một tệp văn bản :)

Ai đó đã đặt câu hỏi về việc tăng kích thước bộ đệm đầu vào Perl trên perlmonks, ai đó trả lời here mà bạn có thể tăng kích thước "$ /" và do đó tăng kích thước bộ đệm , tuy nhiên, từ perldoc:

Đặt $/để tham chiếu đến số nguyên, vô hướng có thể chuyển thành số nguyên sẽ cố đọc bản ghi thay vì dòng, với kích thước bản ghi tối đa là tài liệu tham khảo d số nguyên.

Vì vậy, tôi cho rằng điều này không thực sự làm tăng kích thước bộ đệm mà Perl sử dụng để đọc trước từ đĩa khi sử dụng tiêu biểu:

while(<>) { 
    #do something with $_ here 
    ... 
} 

"line-by-line" thành ngữ.

Bây giờ có thể khác "đọc một bản ghi tại một thời điểm và sau đó phân tích thành dòng" phiên bản của mã trên sẽ nhanh hơn nói chung và bỏ qua vấn đề cơ bản với thành ngữ chuẩn và không thể thay đổi kích thước bộ đệm mặc định (nếu điều đó thực sự không thể), vì bạn có thể đặt "kích thước bản ghi" thành bất kỳ thứ gì bạn muốn và sau đó phân tích từng bản ghi thành từng dòng riêng lẻ, và hy vọng rằng Perl làm điều đúng và kết thúc bằng một hệ thống gọi cho mỗi bản ghi, nhưng nó làm tăng thêm độ phức tạp và tất cả những gì tôi thực sự muốn làm là đạt được hiệu suất dễ dàng bằng cách tăng bộ đệm được sử dụng trong ví dụ trên lên kích thước lớn, 64K hoặc thậm chí điều chỉnh kích thước bộ đệm đó thành kích thước tối ưu cho những lần đọc dài sử dụng tập lệnh thử nghiệm trên hệ thống của tôi, mà không cần thêm rắc rối.

Mọi thứ tốt hơn rất nhiều trong Java theo như hỗ trợ thẳng về phía trước để tăng kích thước bộ đệm.

Trong Java, tôi tin rằng kích thước bộ đệm mặc định hiện tại mà java.io.BufferedReader sử dụng cũng là 8192 byte, mặc dù tài liệu tham khảo cập nhật trong tài liệu JDK là tương đối, ví dụ: 1.5 tài liệu chỉ nói:

Kích thước bộ đệm có thể được chỉ định hoặc kích thước mặc định có thể được chấp nhận. Mặc định là đủ lớn cho hầu hết các mục đích.

May mắn với Java bạn không cần phải tin tưởng các nhà phát triển JDK đã thực hiện quyết định đúng đắn cho các ứng dụng của bạn và có thể thiết lập kích thước bộ đệm riêng của bạn (64K trong ví dụ này):

import java.io.BufferedReader; 
[...] 
reader = new BufferedReader(new InputStreamReader(fileInputStream, "UTF-8"), 65536); 
[...] 
while (true) { 
       String line = reader.readLine(); 
       if (line == null) { 
        break; 
       } 
       /* do something with the line here */ 
       foo(line); 
} 

Chỉ có rất nhiều hiệu suất bạn có thể vắt ra khỏi việc phân tích một dòng tại một thời điểm, ngay cả với bộ đệm khổng lồ và phần cứng hiện đại, và tôi chắc chắn có nhiều cách để có được hiệu suất của việc đọc trong một tệp bằng cách đọc nhiều các bản ghi dòng và bẻ khóa mỗi lần vào các thẻ sau đó thực hiện các công cụ với các mã thông báo đó một lần cho mỗi bản ghi, nhưng chúng thêm các trường hợp phức tạp và cạnh (mặc dù nếu có một giải pháp thanh lịch trong Java thuần túy (chỉ sử dụng các tính năng có trong JDK 1.5) sẽ rất hay khi biết). Tăng kích thước bộ đệm trong Perl sẽ giải quyết được 80% vấn đề hiệu suất cho Perl ít nhất, trong khi vẫn giữ mọi thứ thẳng về phía trước.

Câu hỏi của tôi là:

Có cách nào để điều chỉnh kích thước bộ đệm trong Perl cho điển hình "line-by-line" thành ngữ trên, tương tự như thế nào kích thước bộ đệm được tăng lên trong ví dụ Java?

Trả lời

6

Bạn có thể ảnh hưởng đến bộ đệm, giả sử bạn đang chạy trên O/S hỗ trợ setvbuf. Xem tài liệu cho IO::Handle. Bạn không cần phải tạo một đối tượng IO :: Handle như trong tài liệu nếu bạn đang sử dụng perl 5.10; tất cả các xử lý là ngầm IO :: Xử lý kể từ khi phát hành.

use 5.010; 
use strict; 
use warnings; 

use autodie; 

use IO::Handle '_IOLBF'; 

open my $handle, '<:utf8', 'foo'; 

my $buffer; 
$handle->setvbuf($buffer, _IOLBF, 0x10000); 

while (my $line = <$handle>) { 
    ... 
} 
+0

Thật tuyệt khi đăng liên kết đến một số thông tin khác về xử lý Perl 5.10. –

+0

Điều duy nhất khác với các phiên bản trước đó là xử lý được may mắn vào gói IO :: Handle. Đó là sự khác biệt duy nhất. Đặc biệt, chỉ việc mở một tệp không có nghĩa là bạn có thể gọi bất kỳ phương thức nào trên tay cầm. Bạn phải "sử dụng IO :: Xử lý" để các phương thức được xác định. –

+0

Điều đó không mới trong 5,10; filehandles đã được may mắn vào IO :: Xử lý trong một thời gian dài (hoặc, cho tương thích ngược, vào FileHandle nếu đã được nạp). Nhưng như Elliot nói, các phương thức không được định nghĩa trừ khi bạn sử dụng IO :: Handle. – ysth

2

Không, đó không phải là (viết tắt của biên dịch lại một perl sửa đổi), nhưng bạn có thể đọc toàn bộ tập tin vào bộ nhớ, sau đó làm việc từng dòng từ đó:

use File::Slurp; 
my $buffer = read_file("filename"); 
open my $in_handle, "<", \$buffer; 
while (my $line = readline($in_handle)) { 
} 

Lưu ý rằng perl trước 5.10 mặc định để sử dụng bộ đệm stdio ở hầu hết các nơi (nhưng thường gian lận và truy cập trực tiếp bộ đệm, không thông qua thư viện stdio), nhưng trong 5.10 và sau đó mặc định là hệ thống lớp perlio riêng của nó. Sau này dường như sử dụng bộ đệm 4k theo mặc định, nhưng việc viết một lớp cho phép định cấu hình điều này sẽ là tầm thường (khi bạn tìm ra cách viết một lớp: xem perldoc perliol).

1

Cảnh báo, mã sau chỉ được kiểm tra ánh sáng. Đoạn mã dưới đây là ảnh đầu tiên ở một hàm sẽ cho phép bạn xử lý một dòng tệp theo dòng (do đó tên hàm) với kích thước bộ đệm có thể xác định người dùng. Phải mất đến bốn đối số:

  1. một filehandle mở (mặc định là STDIN)
  2. một kích thước bộ đệm (mặc định là 4k)
  3. một tham chiếu đến một biến để lưu trữ các dòng trong (mặc định là $_)
  4. một chương trình con ẩn danh để gọi trên tệp (mặc định sẽ in dòng).

Đối số là vị trí ngoại trừ đối số cuối cùng luôn có thể là trình con ẩn danh. Các dòng được tự động chomped.

lỗi kiến:

  • có thể không hoạt động trên các hệ thống mà thức ăn dòng là kết thúc của nhân vật dòng
  • sẽ có khả năng thất bại khi kết hợp với một từ vựng $_ (giới thiệu trong Perl 5.10)

Bạn có thể xem từ một số strace mà nó đọc tệp có kích thước bộ đệm được chỉ định. Nếu tôi thích cách thử nghiệm, bạn có thể thấy điều này trên CPAN sớm.

#!/usr/bin/perl 

use strict; 
use warnings; 
use Scalar::Util qw/reftype/; 
use Carp; 

sub line_by_line { 
    local $_; 
    my @args = \(
     my $fh  = \*STDIN, 
     my $bufsize = 4*1024, 
     my $ref  = \$_, 
     my $coderef = sub { print "$_\n" }, 
    ); 
    croak "bad number of arguments" if @_ > @args; 

    for my $arg_val (@_) { 
     if (reftype $arg_val eq "CODE") { 
      ${$args[-1]} = $arg_val; 
      last; 
     } 
     my $arg = shift @args; 
     $$arg = $arg_val; 
    } 

    my $buf; 
    my $overflow =''; 
    OUTER: 
    while(sysread $fh, $buf, $bufsize) { 
     my @lines = split /(\n)/, $buf; 
     while (@lines) { 
      my $line = $overflow . shift @lines; 
      unless (defined $lines[0]) { 
       $overflow = $line; 
       next OUTER; 
      } 
      $overflow = shift @lines; 
      if ($overflow eq "\n") { 
       $overflow = ""; 
      } else { 
       next OUTER; 
      } 
      $$ref = $line; 
      $coderef->(); 
     } 
    } 
    if (length $overflow) { 
     $$ref = $overflow; 
     $coderef->(); 
    } 
} 

my $bufsize = shift; 

open my $fh, "<", $0 
    or die "could not open $0: $!"; 

my $count; 
line_by_line $fh, sub { 
    $count++ if /lines/; 
}, $bufsize; 

print "$count\n"; 
+1

Tôi bắt đầu chơi với 'sysread' để trả lời cho câu hỏi này, nhưng tôi không thể vui về cách phân tích * dòng * sau đó. Điều này có vẻ đầy hứa hẹn, nhưng tôi tự hỏi nếu nó vẫn không bật ra được chậm hơn so với thực hiện được xây dựng trong Perl (đệm mặc dù). – Telemachus

+1

Hey, tôi không bao giờ tuyên bố nó sẽ là __fast__, chỉ là nó sẽ đọc các tập tin với kích thước bộ đệm được chỉ định. Điều đó nói rằng, tôi sẽ chuẩn nó chống lại các thành ngữ phổ biến và kết quả sẽ là một phần của tài liệu. –

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