在巨大文件打开时复制数据

时间:2015-08-21 12:49:49

标签: perl caching

我正在尝试使用Perl将大文件中的数据合并到组合文件中。

文件将处于打开状态,并且不断向文件中添加大量数据。每分钟追加约50,000行。

文件存储在由10到30台机器访问的网络共享文件夹中。

这些是JMeter生成的JTL文件。

此合并每分钟运行约6或7小时,所用时间不应超过30至40秒。

该进程由Linux机器中部署的Web应用程序每分钟触发一次。

我编写了一个脚本,将单个文件添加的最后一行存储在单独文件中的合并文件中。

这可以在15分钟内正常工作,但不断增加合并时间。

我的脚本

#!/usr/bin/perl

use File::Basename;
use File::Path;

$consolidatedFile = $ARGV[0];
$testEndTimestamp = $ARGV[1];
@csvFiles         = @ARGV[ 2 .. $#ARGV ];
$testInProcess    = 0;
$newMerge         = 0;
$lastLines        = "_LASTLINES";
$lastLine         = "_LASTLINE";

# time() gives current time timestamp
if ( time() <= $testEndTimestamp ) {
    $testInProcess = 1;
}

# File exists, has a size of zero
if ( -z $consolidatedFile ) {
    mkdir $consolidatedFile . $lastLines;
    $newMerge = 1;
}

open( CONSOLIDATED, ">>" . $consolidatedFile );

foreach my $file ( @csvFiles ) {

    open( INPUT, "<" . $file );
    @linesArray = <INPUT>;
    close INPUT;

    if ( $newMerge ) {

        print CONSOLIDATED @linesArray[ 0 .. $#linesArray - 1 ];

        open my $fh, ">", $consolidatedFile . $lastLines . "/" . basename $file . $lastLine;
        print $fh $linesArray[ $#linesArray - 1 ];
        close $fh;
    }
    else {

        open( AVAILABLEFILE, "<" . $consolidatedFile . $lastLines . "/" . basename $file . $lastLine );
        @lineArray = <AVAILABLEFILE>;
        close AVAILABLEFILE;

        $availableLastLine = $lineArray[0];

        open( FILE, "<" . $file );
        while ( <FILE> ) {
            if ( /$availableLastLine/ ) {
                last;
            }
        }
        @grabbed = <FILE>;
        close( FILE );

        if ( $testInProcess ) {

            if ( $#grabbed > 0 ) {

                pop @grabbed;
                print CONSOLIDATED @grabbed;

                open( AVAILABLEFILE, ">" . $consolidatedFile . $lastLines . "/" . basename $file . $lastLine );
                print AVAILABLEFILE $grabbed[ $#grabbed - 1 ];
            }
            close AVAILABLEFILE;
        }
        else {

            if ( $#grabbed >= 0 ) {
                print CONSOLIDATED @grabbed;
            }
        }
    }
}

close CONSOLIDATED;

if ( !$testInProcess ) {

    rmtree $consolidatedFile . $lastLines;
}

我需要优化脚本以减少时间。

是否可以将最后一行存储在缓存中?

有人可以为这种类型的合并提出另一种方法吗?

  

另一个脚本,它将最后一行存储在缓存中而不是文件中。

即使这样也不会在1分钟内完成合并。

#!/usr/bin/perl

use CHI;

use File::Basename;
use File::Path;

my $cache = CHI->new(
driver   => 'File',
root_dir => '/path/to/root'
);

$consolidatedFile = $ARGV[0];
$testEndTimestamp = $ARGV[1];
@csvFiles         = @ARGV[ 2 .. $#ARGV ];
$testInProcess    = 0;
$newMerge         = 0;
$lastLines        = "_LASTLINES";
$lastLine         = "_LASTLINE";

# time() gives current time timestamp
if ( time() <= $testEndTimestamp ) {
    $testInProcess = 1;
}

# File exists, has a size of zero
if ( -z $consolidatedFile ) {
    $newMerge = 1;
}

open( CONSOLIDATED, ">>" . $consolidatedFile );

foreach my $file (@csvFiles) {

    $fileLastLineKey =
      $consolidatedFile . $lastLines . "_" . basename $file . $lastLine;

    open( INPUT, "<" . $file );
    @linesArray = <INPUT>;
close INPUT;

if ($newMerge) {

    print CONSOLIDATED @linesArray[ 0 .. $#linesArray - 1 ];
    $fileLastLine = $linesArray[ $#linesArray - 1 ];
    $cache->set( $fileLastLineKey, $fileLastLine );

}
else {

    $availableLastLine = $cache->get($fileLastLineKey);

    open( FILE, "<" . $file );
    while (<FILE>) {
        if (/$availableLastLine/) {
            last;
        }
    }
    @grabbed = <FILE>;
    close(FILE);

    if ($testInProcess) {

        if ( $#grabbed > 0 ) {

            pop @grabbed;
            print CONSOLIDATED @grabbed;

            $fileLastLine = $grabbed[ $#grabbed - 1 ];
            $cache->set( $fileLastLineKey, $fileLastLine );
        }
    }
    else {

        if ( $#grabbed >= 0 ) {
            print CONSOLIDATED @grabbed;
            $cache->remove($fileLastLineKey);
        }
    }
}
}

close CONSOLIDATED;

我正在考虑将文件从最后一行读取到所需行,并将这些行复制到合并文件中。

有人可以建议吗???

1 个答案:

答案 0 :(得分:0)

您可能想尝试在binmode中打开文件并在循环中以块状方式读取它。这通常可以显着提高性能。以下函数是一个示例,这里我将数组中文件的最大$ maxblocks块放在作为引用传递的数组中的块$ offset on上。请注意,当文件不够大时,最后一个块可能不包含整个$ block字节。

sub file2binarray {
  my $file=shift;
  my $array=shift;
  my $maxblocks=shift;
  my $offset=shift;

  my $block=2048;

  $offset=0 if ((!defined($offset))  || ($offset   !~/^\s*\d+\s*$/o));
  $maxblocks="ALL" 
            if (!defined($maxblocks) || ($maxblocks!~/^\s*\d+\s*$/o)); 

  my $size=(stat($file))[7];
  my $mb=$size/$block;
  $mb++ if ($mb*$block<$size);
  $maxblocks=$mb-$offset if(($maxblocks eq "ALL")||
                             ($maxblocks>$mb-$offset));
  $offset*=$block;
  open(IN,"$file") || die("Cannot open file <$file>\n");
  binmode(IN);
  $bytes_read=$block;
  seek(IN,$offset,0);

  my ($blk,$bytes_read,$buffer)=(0,0,"");

  while (($bytes_read==$block)&& ($blk<$maxblocks)){
      $bytes_read=sysread(IN,$buffer,$block);
      push(@$array,$buffer);
      $blk++;
  }

  close(IN);

}

以1来读取整个文件,例如你这么称呼它

my @array;
my $filename="somefile";
file2binarray ($filename,\@array,"ALL",0);

但是你可能宁愿在循环中调用它,并在偏移量上记录一些,并在后续调用之间解析数组。 希望这会有所帮助。