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

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

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

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

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

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

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

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

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

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

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

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

В 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?

3 ответа

Вы можете повлиять на буферизацию, предполагая, что вы работаете на O/S, который поддерживает setvbuf. См. Документацию для IO:: Handle. Вам не нужно явно создавать объект IO:: Handle, как в документации, если вы используете perl 5.10; все дескрипторы неявно IO:: Ручки с момента выпуска.

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


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

  • открытая дескриптор файла (по умолчанию - STDIN)
  • размер буфера (по умолчанию - 4k)
  • ссылка на переменную для хранения строки в (по умолчанию - $_)
  • анонимная подпрограмма для вызова файла (по умолчанию печать строки).

Аргументы являются позиционными с исключением, что последний аргумент всегда может быть анонимной подпрограммой. Линии автоматически сжимаются.

Вероятные ошибки:

  • может не работать в системах, где фид строки является символом конца строки
  • скорее всего не удастся в сочетании с лексическим $_ (введенным в 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";

licensed under cc by-sa 3.0 with attribution.