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

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

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

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

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

Таким образом, для файла размером 50 000 000 строк с 60 символами в строке (включая новую строку в конце) с буфером 8 КБ он сделает 366211 системных вызовов для чтения файла 2,8 ГБ. Кроме того, вы можете подтвердить это поведение, посмотрев на дельту чтения ввода-вывода диска (по крайней мере, в Windows верхняя часть * nix показывает то же самое, я уверен) в списке процессов диспетчера задач в качестве вашей программы Perl чтение в текстовом файле занимает 10 минут :)

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

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

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

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

идиома «построчно».

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

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

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

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

К счастью, с Java вам не нужно доверять разработчикам JDK, которые приняли правильное решение для вашего приложения и могут установить свой собственный размер буфера (64 КБ в этом примере):

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?


person cons    schedule 09.08.2009    source источник


Ответы (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> ) {
    ...
}
person Elliot Shank    schedule 09.08.2009
comment
Было бы неплохо разместить ссылку на дополнительную информацию о дескрипторах Perl 5.10. - person Brad Gilbert; 10.08.2009
comment
Единственное, что отличается от более ранних версий, - это то, что дескрипторы добавлены в пакет IO :: Handle. Это / единственная / разница. В частности, простое открытие файла не означает, что вы можете вызывать какие-либо методы дескриптора. Вы должны использовать IO :: Handle, чтобы определить методы. - person Elliot Shank; 11.08.2009
comment
Это не новость в 5.10; дескрипторы файлов были благословлены в IO :: Handle в течение длительного времени (или, для обратной совместимости, в FileHandle, если он был загружен). Но, как говорит Эллиот, методы не определены, если вы не используете IO :: Handle. - person ysth; 07.09.2009
comment
perldelta для v5.13.8 говорит: Когда вызов метода для дескриптора файла завершится, потому что метод не может быть разрешен, а IO::File не был загружен, Perl теперь загружает IO::File через require и снова пытается разрешить метод . IO::File является подклассом IO::Handle, поэтому оба загружаются по запросу (а также IO::Seekable), и их методы могут использоваться без явного оператора use. Первым публичным выпуском с этой возможностью является perl v5.14.0 в 2011 году. - person Borodin; 16.05.2018

Нет, его нет (если не считать перекомпиляции модифицированного 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. Последний, похоже, по умолчанию использует буфер 4k, но написание слоя, позволяющего его настраивать, должно быть тривиальным (как только вы поймете, как писать слой: см. perldoc perliol).

person ysth    schedule 09.08.2009

Предупреждение, следующий код был протестирован только на свет. Приведенный ниже код - это первый снимок функции, которая позволит вам обрабатывать файл построчно (отсюда и название функции) с определяемым пользователем размером буфера. Требуется до четырех аргументов:

  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";
person Chas. Owens    schedule 09.08.2009
comment
Я начал играть с sysread в ответ на этот вопрос, но после этого не мог получить удовольствие от того, как разбирать строки. Это выглядит многообещающе, но мне интересно, не окажется ли он все же медленнее, чем встроенная реализация Perl (несмотря на буферизацию). - person Telemachus; 09.08.2009
comment
Эй, я никогда не утверждал, что он будет быстрым, просто он будет читать файлы с указанным размером буфера. Тем не менее, я собираюсь сравнить его с общепринятой идиомой, и результаты будут частью документации. - person Chas. Owens; 09.08.2009

Я некропостинг, так как это появилось в этой ветке perlmonks

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

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

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

    use open ':buffersize(65536)';
person Jeff Fisher    schedule 16.05.2018