Чтение бинарного файла в perl - нехватка памяти

Я хочу прочитать большой двоичный файл (500 МБ) и получить определенные байты, расположенные после заголовка, который повторяется каждые 5000 байт. Для этого у меня есть короткий фрагмент, читающий файл в двоичном режиме и размером блока 16536.

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

Вот мой код:

use strict;

my $BLOCK_SIZE=16536;

my $fname = $ARGV[0];
my $fparsename = $ARGV[1];
open(F,"<$fname") or die("Unable to open file $fname, $!");
binmode(F);
my $buf;
my $ct=0;
my $byte=0;
my $byte_old=0;
my $byte_cnt=0;
my $byte_lock=0;
my $sample_msb=0;
my $sample_lsb=0;
my $sample_16b=0;
my $out_form='';

open(my $fh, '>', $fparsename) or die "Could not open file '$fparsename' $!";
print $fh ("Sample, Value \n");
close($fh);

while(read(F,$buf,$BLOCK_SIZE,$ct*$BLOCK_SIZE)){
    foreach(split(//, $buf)){

        $byte_old = $byte;
        $byte = ord($_);    # fetch byte (in decimal)


        if (($byte_old == 202) && ($byte == 254)) { # CA = 202, FE = 254
            $byte_cnt = 0;
            $byte_lock = 1;
        }

        if ($byte_lock == 1) {
            $byte_cnt++;
        }

        if ($byte_cnt == 20) {  # 20th byte after CAFE in header
            $sample_msb = $byte;
        }
        if ($byte_cnt == 21) {  # 21th byte after CAFE in header
            $sample_lsb = $byte;
        }

        if (($byte_cnt == 21) && ($byte_lock == 1)) {   # lock down and concatenate
            $byte_lock = 0;
            $byte_cnt = 0;
            $sample_16b = sprintf("%X", $sample_msb) . sprintf("%X", $sample_lsb);
            $out_form = sprintf("%d, %s \n", $ct++, $sample_16b);

            open(my $fh, '>>', $fparsename) or die "Could not open file '$fparsename' $!";
            printf $fh $out_form;
            close($fh);
        }

    }
    $ct++;

}
close(F);
close($fh);

person Deyan Levski    schedule 15.02.2017    source источник
comment
Вместо того, чтобы использовать десятичные числа и комментировать их CA = 202, FE = 254, вы должны использовать 0xCA и 0xFE.   -  person Borodin    schedule 15.02.2017


Ответы (1)


Ну, во-первых, то, что ест память, это то, что хранится в памяти. Закрытие и открытие дескриптора файла не имеет никакого значения, потому что записи будут сбрасываться по мере необходимости.

И я не могу воспроизвести вашу проблему, так как у меня нет подходящего файла примера.

Однако я думаю, что ваша проблема в том, что вы неправильно указали read:

СМЕЩЕНИЕ может быть указано, чтобы поместить считанные данные в какое-то место в строке, отличное от начала. Отрицательное значение OFFSET определяет размещение на указанном количестве символов, считая в обратном порядке от конца строки. Положительное значение OFFSET, превышающее длину SCALAR, приводит к тому, что строка дополняется до требуемого размера байтами "\0" перед добавлением результата чтения.

Итак... ваше "последнее" чтение из файла создаст строку длиной 500 МБ, которая в основном состоит из нулевых байтов. (вероятно, это будет больше, чем 500 МБ фактической памяти по разным причинам)

Это проще всего исправить, просто убрав смещение в файле read.

Но в противном случае:

Поэтому я бы внимательно рассмотрел ваши переменные - в частности, вы рассматриваете их все за пределами вашего цикла и означает, что они могут объединяться, а не перезаписываться.

Я бы также посоветовал, если вы перебираете файл по байтам, вы можете значительно упростить его, установив $/. Например.

local $/ = \16536

while ( my $buf = <$input> ) { 

}

Из perlvar

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

(Или, как отмечено в комментариях, учитывая, что ваши «фрагменты» составляют 5000 байт, тогда, вероятно, будет разумно установить local $/ = \5000).

И я бы также предложил - используйте 3 arg open, а не 2 arg:

open ( my $input, '<:raw', $fname ) or die $!; 
person Sobrique    schedule 15.02.2017
comment
Не могли бы вы объяснить, почему вы выбрали \16536 как $/. - person simbabque; 15.02.2017
comment
Я бы установил local $/ = \5000, чтобы определенные байты появлялись один раз в каждом фрагменте. - person Borodin; 15.02.2017
comment
@simbabque - потому что это то, что ОП установил в качестве своего размера чтения. Как отмечает Бородин, 5000, вероятно, более разумно, учитывая вариант использования OP. - person Sobrique; 15.02.2017
comment
О, так это количество байтов, которые должны быть прочитаны. Я не знал об этом. Думаю, это где-то задокументировано. Может быть, вы тоже можете добавить это. - person simbabque; 15.02.2017
comment
Как предположил @Sobrique, проблема заключалась в том, как я читал файл. Я использовал ваш подход, установив локальный $/ = \16536. 16536 — это количество байтов, которое я прочитал и проверил на наличие начального шаблона 0xCA 0xFE. Спасибо за вашу помощь. - person Deyan Levski; 16.02.2017