0

改行の $scaler の最後の文字をチェックするために、次のことを考え出しました。

if( $buffer !~ /\n$/ ) {
if( substr( $buffer, -1, 1 ) !~ /\n/ ) {
if( substr( $buffer, -1, 1 ) ne '\n' ) {

私ができるより速い方法はありますか?$buffer スカラーのサイズは大きくなる可能性があり、サイズが大きくなるほど、これらの条件の実行に時間がかかることに気付きました。$buffer の長さを含む別のスカラーがあります。

ありがとう

完全なコード:

#!/usr/bin/perl
use strict;
use warnings;
use Fcntl qw();
use Time::HiRes qw( gettimeofday tv_interval );

use constant BUFSIZE => 2 ** 21; # 2MB worked best for me, YMMV.

die "ERROR: Missing filename" if( !$ARGV[0] );

my $top = [gettimeofday];
sysopen( my $fh, $ARGV[0], Fcntl::O_RDONLY | Fcntl::O_BINARY ) or
  die "ERROR: Unable to open $ARGV[0], because $!\n";
open my $output, ">", "/dev/null";  # for 'dummy' processing

my $size = -s $ARGV[0];
my $osiz = $size;
my( $buffer, $offset, $lnCtr ) = ( "", "", 0 );
while( $size ) {
    my $read = sysread( $fh, $buffer, BUFSIZE, length($offset) );
    $size -= $read;
    my @lines = split /\n/, $buffer;
    if( substr( $buffer, -1, 1 ) ne "\n" ) {
        $offset = pop( @lines );
    } else {
        $offset = "";
    }
    for my $line ( @lines ) {
        processLine( \$line );
        $lnCtr++;
    }
    $buffer = $offset if( $offset );
}
close $fh;
print "Processed $lnCtr lines ($osiz bytes) in file: $ARGV[0] in ".
      tv_interval( $top ).
      " secs.\n";
print "Using a buffered read of ".BUFSIZE." bytes.  -  JLB\n";

sub processLine {
    if( ref($_[0]) ) {
        print $output ${$_[0]}."\n";
    } else {
        print $output $_[0]."\n";
    }
    return 0;
}

これをより速く実行しようとする試みで、その「収益が減少するポイント」に到達したと思います。私のRAID5 SSDがデータをフェッチできるのと同じくらい速くデータを読み込むことができるようになりました。ご覧のとおり、chomp() を使用しなかったのには理由があります。入力には何十万もの改行が含まれる可能性があり、処理のために行を分割できるようにするために、改行を保持する必要があります。

./fastread.pl newdata.log ファイル内の 516670 行 (106642635 バイト) を処理: newdata.log 0.674738 秒。2097152 バイトのバッファリングされた読み取りを使用します。- JLB

4

5 に答える 5

3

Perl には 2 つの文字列格納形式があります。

形式の 1 つは、同じバイト数 (1) を使用して、文字列に含めることができる各文字を格納します。そのため、および Perl は文字列が使用するバイト数を追跡​​しているためsubstr($x, -1)、この形式の文字列に対する のパフォーマンスは文字列の長さに依存しません。

前述の形式の問題は、非常に限られた範囲の文字しか格納できないことです。Unicode コード ポイント "Eric" および "Éric" を格納するために使用できますが、"Ελλάδα" には使用できません。必要な場合 (必要でない場合でも)、Perl は文字列の保存形式を別の形式に自動的に切り替えます。

2 番目の形式は、任意の Unicode コード ポイントを文字として格納できます。実際、32 ビットまたは 64 ビットの任意の値を格納できます (perlのビルド設定によって異なります)。欠点は、各文字を格納するために可変数のバイトが使用されることです。したがって、Perl は文字列全体で使用されるバイト数を知っていても、最初の文字以外の文字がどこから始まるかはわかりません。* 最後の文字を見つけるには、文字列全体をスキャンする必要があります。

そうは言っても、ストレージ形式のプロパティにより、文字列の最後の文字を定数時間で見つけるのは実際には非常に簡単です。

use Inline C => <<'__END_OF_C__';

   # O(1) version of substr($x,-1)
   SV* last_char(SV* sv) {
      STRLEN len;
      const char* s = SvPV(sv, len);

      if (!len)
         return newSVpvn("", 0);

      {
         const U32 utf8 = SvUTF8(sv);
         const char* p = s+len-1;         
         if (utf8) {
            while (p != s && (*p & 0xC0) != 0xC0)
               --p;
         }

         return newSVpvn_utf8(p, s+len-p, utf8);
      }
   }

__END_OF_C__

* — char 位置からバイト位置へのマッピングのキャッシュを保持します。


クリーンアップできるコードを示したので、最後の文字で改行を確認する必要さえありません。

sub processLine {
   print $_[0] $_[1];
}


open(my $fh, '<:raw', $ARGV[0])
   or die("Can't open $ARGV[0]: $!\n");

my $buffer = '';
my $lnCtr = 0;
while (1) {
   my $rv = sysread($fh, $buffer, BUFSIZE, length($buffer));
   die $! if !defined($rv);
   last if !$rv;

   while ($buffer =~ s/(.*\n)//) {
      processLine($1);
      ++$lnCtr;
   }
}

if (length($buffer)) {
   processLine($output, $buffer);
   ++$lnCtr;
}

ノート:

  • の必要はありませんsysopenopenはより簡単です。
  • に渡す$buffersysread、 を使用しても意味がありませんlength($offset)
  • ご覧のとおり、$offsetそのコピーは完全に不要です。
  • var を sub に渡してもコピーされないため、参照を渡す必要はありません。
  • processLine改行が必要ない場合は、s/(.*)\n//代わりに使用してください。
于 2013-03-01T07:30:33.910 に答える
1

ベンチマークは次のとおりです。

#!/usr/bin/perl 
use strict;
use warnings;
use Benchmark qw(:all);

my $buffer = 'abc'x10_000_000;
$buffer .= "\n";
my $count = -2;
cmpthese($count, {
    'regex' => sub {
        if ($buffer !~ /\n$/) { }
    },
    'substr + regex' => sub {
        if (substr($buffer, -1, 1) !~ /\n$/) { }
    },
    'substr + ne' => sub {
        if (substr($buffer, -1, 1) ne "\n") { }
    },
    'chomp' => sub {
        if (chomp $buffer) { }
    },
});

出力:

                     Rate substr + regex  substr + ne         regex        chomp
substr + regex  6302468/s             --         -11%          -44%         -70%
substr + ne     7072032/s            12%           --          -37%         -66%
regex          11294695/s            79%          60%            --         -46%
chomp          20910531/s           232%         196%           85%           --

chomp確かに最速の方法です。

于 2013-03-01T08:15:24.807 に答える
1

なぜ速度にこだわるのですか?このコードは、おそらく Devel::NYTProf でプロファイリングされた、測定可能なほど遅いプログラムの一部ですか? そうでない場合は、最も読みやすく、最も慣用的なものを使用することをお勧めします。

if( $buffer !~ /\n$/ )

最終バージョン:

if( substr( $buffer, -1, 1 ) ne '\n' )

改行を一重引用符で囲んで、バックスラッシュと小文字の n で構成される 2 文字の文字列を指定する以外は、これも適切な選択です。おそらく、単一文字が単一引用符で囲まれ、文字列が二重引用符で囲まれている C から来ているのでしょうか? あなたがしたい

if( substr( $buffer, -1, 1 ) ne "\n" )

このバージョン

if( substr( $buffer, -1, 1 ) !~ /\n/ )

1文字の正規表現に対して1文字の文字列をチェックしているため、そうすべきではない正規表現の一致を行っています。次にコードを読んだ人は、それは奇妙だと思い、なぜそんなことをするのか不思議に思うでしょう。また、その速度の問題に戻ると、単一の文字と比較して同等かどうかを比較するよりも、文字列を正規表現と照合する方が遅くなります。

于 2013-03-01T06:10:46.417 に答える
0

あなたはchompを試すことができます。Chompは、行末から削除されたEOL文字の数を返します。

if ( chomp $buffer ) {
    print "You had an LF on the end of \$buffer";
}

もちろん、chompはカウントしたNL文字を削除します。

于 2013-03-01T04:40:17.053 に答える
0

perl は文字列を utf-8 として扱っており、何らかの理由で全体を反復処理する必要があると思われます。

一時的にバイトセマンティクスに切り替えて、最後の文字が改行かどうかを確認できます。

Perl のbytes プラグマperlunicodeについてはドキュメントを参照してください。

于 2013-03-01T04:36:31.547 に答える