簡體   English   中英

如何在Perl中設置文件讀取緩沖區大小以針對大文件進行優化?

[英]How can I set the file-read buffer size in Perl to optimize it for large files?

據我所知,Java和Perl在讀取文件時都很難找到一個適合所有默認緩沖區大小的內容,但是我發現他們的選擇越來越過時,並且在更改默認選擇時遇到問題Perl的。

在Perl的情況下,我認為默認情況下使用8K緩沖區,類似於Java的選擇,我找不到使用perldoc網站搜索引擎(真的是Google)關於如何增加默認文件輸入緩沖區大小的引用, 64K。

從上面的鏈接,以顯示8K緩沖區如何不縮放:

如果行通常每個大約有60個字符,則10,000行文件中包含大約610,000個字符。 通過緩沖逐行讀取文件只需要75次系統調用和75次等待磁盤,而不是10,001。

因此,對於每行60個字符(包括末尾的換行符)的50,000,000行文件,使用8K緩沖區,它將進行366211次系統調用以讀取2.8GiB文件。 順便說一下,您可以通過在任務管理器進程列表中查看磁盤i / o讀取增量(在Windows中至少,* nix中顯示相同的內容,我確定)作為您的Perl程序來確認此行為在文本文件中讀取需要10分鍾:)

有人問起增加了對perlmonks Perl的輸入緩沖區大小的問題,有人說在這里 ,你可以增加“$ /”的尺寸,從而增加緩沖區的大小,但是從的perldoc:

將$ /設置為對整數的引用,包含整數的標量或可轉換為整數的標量將嘗試讀取記錄而不是行,最大記錄大小為引用的整數。

所以我假設這實際上並沒有增加Perl在使用典型時從磁盤讀取時使用的緩沖區大小:

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

“逐行”成語。

現在它可能是一個不同的“一次讀取一條記錄,然后將其解析為行”,上述代碼的版本通常會更快,並繞過標准習慣用語的基礎問題而無法更改默認緩沖區大小(如果確實不可能),因為您可以將“記錄大小”設置為您想要的任何內容,然后將每條記錄解析為單獨的行,並希望 Perl做正確的事情並最終為每條記錄執行一次系統調用,但它增加了復雜性,而我真正想做的就是通過將上面示例中使用的緩沖區增加到相當大的大小(例如64K),甚至將緩沖區大小調整為使用測試進行長讀取的最佳大小來獲得輕松的性能提升我的系統上的腳本,無需額外的麻煩。

對於增加緩沖區大小的直接支持,Java中的情況要好得多。

在Java中,我相信java.io.BufferedReader使用的當前默認緩沖區大小也是8192字節,盡管JDK文檔中的最新引用是模棱兩可的,例如,1.5文檔只說:

可以指定緩沖區大小,或者可以接受默認大小。 對於大多數用途,默認值足夠大。

幸運的是,您不必相信JDK開發人員可以為您的應用程序做出正確的決定,並且可以設置自己的緩沖區大小(在此示例中為64K):

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

即使使用巨大的緩沖區和現代硬件,你也可以擠出一次解析一行的性能,而且我確信有很多方法可以通過閱讀大文件來讀取文件中的每一個性能多行記錄並將每個記錄分解為令牌,然后每個記錄執行一次這些令牌,但是它們增加了復雜性和邊緣情況(盡管如果在純Java中有一個優雅的解決方案(僅使用JDK 1.5中提供的功能)那將是很酷的了解)。 增加Perl中的緩沖區大小至少可以解決Perl 80%的性能問題,同時保持直接的性能。

我的問題是:

有沒有辦法在Perl中為上述典型的“逐行”習慣調整緩沖區大小,類似於Java示例中緩沖區大小的增加?

如果您在支持setvbuf的操作系統上運行,則可以影響緩沖; 請參閱IO::Handle文檔

如果您正在使用perl v5.10或更高版本,那么就不需要像文檔中所描述的那樣顯式創建IO::Handle對象,因為從那個版本開始,所有文件句柄都隱式地保存到IO::Handle對象中。

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> ) {
    ...
}

不,沒有(沒有重新編譯修改過的perl),但你可以將整個文件讀入內存,然后逐行工作:

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

請注意,5.10之前的perl默認在大多數地方使用stdio緩沖區(但經常欺騙和直接訪問緩沖區,而不是通過stdio庫),但在5.10及更高版本中默認使用自己的perlio層系統。 后者似乎默認使用4k緩沖區,但編寫一個允許配置它的層應該是微不足道的(一旦你弄清楚如何編寫一個層:請參閱perldoc perliol )。

警告,以下代碼僅經過了輕度測試。 下面的代碼是一個函數的第一個鏡頭,它允許您逐行處理文件(因此函數名稱),具有用戶可定義的緩沖區大小。 它最多需要四個參數:

  1. 打開文件句柄(默認為STDIN
  2. 緩沖區大小(默認為4k)
  3. 對存儲該行的變量的引用(默認為$_
  4. 一個匿名子程序來調用該文件(默認打印該行)。

參數是位置的,但最后一個參數可能始終是匿名子例程。 線條是自動扼殺的。

可能的錯誤:

  • 可能無法在換行符為行尾字符的系統上運行
  • 結合詞匯$_ (在Perl 5.10中引入)可能會失敗

您可以從一個strace看到它讀取具有指定緩沖區大小的文件。 如果我喜歡測試的方式,你很快就會在CPAN上看到這個。

#!/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";

因為這出現在這個perlmonks線程上, 所以我是necroposting

使用PerlIO在perls上使用setvbuf是不可能的,這是自5.8.0版以來的默認值。 但是,CPAN上有PerlIO :: buffersize模塊,允許您在打開文件時設置緩沖區大小:

    open my $fh, '<:buffersize(65536)', $filename;

IIRC,您還可以在腳本開頭使用它來設置任何新文件的默認值:

    use open ':buffersize(65536)';

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM