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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

К счастью, с 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 ]

7 голосов
/ 09 августа 2009

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

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

1 голос
/ 09 августа 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";
0 голосов
/ 16 мая 2018

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

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

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

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

    use open ':buffersize(65536)';
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...