Как установить размер буфера чтения файлов в Perl, чтобы оптимизировать его для больших файлов?

Я понимаю, что как Java, так и Perl пытаются довольно трудно найти один размер-подходит для всех размеров буфера по умолчанию при чтении в файлах, но я нахожу их выбор все более устаревшим, и у меня проблема с изменением выбора по умолчанию, когда дело доходит до Perl.

в случае Perl, который, я считаю, использует 8K буферы по умолчанию, подобно выбору Java, я не могу найти ссылку с помощью поисковой системы веб-сайта perldoc (действительно Google) о том, как увеличить размер входного буфера файла по умолчанию, чтобы сказать, 64K.

из приведенной выше ссылки, чтобы показать, как буферы 8K не масштабируются:

Если строки обычно имеют около 60 символов каждый, то файл 10,000-line имеет около 610,000 символов в нем. Чтение файла по строкам с буферизацией требует только 75 системных вызовов и 75 ожиданий диска вместо 10,001.

So для файла строки 50,000,000 с 60 символами на строку (включая новую строку на end), с буфером 8K, он собирается сделать 366211 системных вызовов для чтения файла 2.8 GiB. В стороне, вы можете подтвердить это поведение, посмотрев на Дельта чтения ввода-вывода диска (по крайней мере, в Windows, top in *nix показывает то же самое, я уверен) в списке процессов диспетчера задач, поскольку ваша программа Perl занимает 10 минут, чтобы прочитать текстовый файл :)

кто-то задал вопрос об увеличении размера входного буфера Perl на perlmonks, кто-то ответил здесь что вы могли бы увеличьте размер "$ / " и, таким образом, увеличьте размер буфера, однако из perldoc:

установка $ / на ссылку на целое число, скаляр, содержащий целое число, или скаляр, конвертируемый в целое число, будет пытаться читать записи вместо строк, при этом максимальный размер записи является ссылочным целым числом.

поэтому я предполагаю, что это фактически не увеличивает размер буфера, который Perl использует для чтения вперед с диска при использовании типично:

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

идиома"строка за строкой".

теперь может быть, что другой" прочитать запись за раз, а затем разобрать ее на строки "версия вышеуказанного кода будет быстрее в целом, и обойти основную проблему со стандартной идиомой и не в состоянии изменить размер буфера по умолчанию (если это действительно невозможно), потому что вы можете установить "размер записи" на все, что вы хотите, а затем разобрать каждую запись на отдельные строки, и Надежда что Perl делает правильную вещь и в конечном итоге делает один системный вызов на запись, но это добавляет сложности, и все, что я действительно хочу сделать, это получить легкий прирост производительности, увеличив буфер, используемый в приведенном выше примере, до разумно большого размера, скажем, 64K, или даже настроив этот размер буфера на оптимальный размер для длительного чтения с помощью тестового сценария в моей системе, без дополнительных хлопот.

в Java все намного лучше, поскольку прямая поддержка увеличение размера буфера идет.

в Java я считаю текущий размер буфера по умолчанию, что java.Ио.BufferedReader использует также 8192 байта, хотя последние ссылки в документах JDK неоднозначны, например, 1.5 docs говорят только:

может быть указан размер буфера или может быть принят размер по умолчанию. Значение по умолчанию достаточно велико для большинства целей.

к счастью, с Java вам не нужно доверять разработчикам 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 решило бы 80% проблемы производительности Perl по крайней мере, сохраняя вещи прямо вперед.

мой вопрос:

есть ли способ настроить размер буфера в Perl для приведенной выше типичной идиомы "строка за строкой", аналогично тому, как размер буфера был увеличен в примере Java?

4 ответов


вы можете повлиять на буферизацию, если вы работаете в ОС, которая поддерживает 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) ) {
}

обратите внимание, что perl до 5.10 по умолчанию использовал буферы stdio в большинстве мест (но часто обманывал и обращался к буферам напрямую, а не через библиотеку stdio), но в 5.10 и позже по умолчанию используется собственная система perlio layer. Последнее представляется использовать 4К буфер по умолчанию, но запись слоя, который позволяет настроить это должно быть тривиально (как только вы поймете, как написать слой: см. 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, "<", 
    or die "could not open : $!";

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

print "$count\n";

Я некропостинг, так как это придумал этот поток perlmonks

невозможно использовать setvbuf на perls, используя PerlIO, который по умолчанию с версии 5.8.0. Тем не менее, есть PerlIO:: buffersize модуль на CPAN, который позволяет установить размер буфера при открытии файла:

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

IIRC, вы также можете установить значение по умолчанию для любых новых файлов, используя это в начале вашего скрипта:

    use open ':buffersize(65536)';