2009-06-15 18 views
8

私は大規模な固定幅のデータファイルを処理するいくつかのPerlスクリプトに取り組んでおり、各データレコードから小さな部分文字列を抽出しています。データレコードを@_配列にコピーするオーバーヘッドのため、メソッド呼び出しに部分文字列の抽出を委任するのはコストがかかると想像していました。そこで、(a)substr()への直接呼び出し、(b)データレコードを文字列として渡すメソッド呼び出し、(c)データレコードを参照渡しするメソッド呼び出しを比較するために以下を実行しました。Perlサブルーチンに引数を渡す際に、データコピーのパフォーマンスが低下しますか?

use strict; 
use warnings; 
use Benchmark qw(timethese); 

my $RECORD = '0' x 50000; 

my $direct = sub { my $v = substr($RECORD, $_, 1) for 0..999 }; 
my $byVal = sub { my $v = ByVal ($RECORD, $_) for 0..999 }; 
my $byRef = sub { my $v = ByRef (\$RECORD, $_) for 0..999 }; 

sub ByVal { return substr( $_[0], $_[1], 1) } 
sub ByRef { return substr(${$_[0]}, $_[1], 1) } 

timethese(10000, { 
    direct => $direct, 
    byVal  => $byVal, 
    byRef  => $byRef, 
}); 

my $byVal2loc = sub { my $v = ByVal2loc($RECORD, $_) for 0..999 }; 
my $byRef2loc = sub { my $v = ByRef2loc(\$RECORD, $_) for 0..999 }; 

sub ByVal2loc { my $arg = shift; return substr( $arg, $_[0], 1) } 
sub ByRef2loc { my $arg = shift; return substr($$arg, $_[0], 1) } 

timethese($ARGV[0], { 
    byVal2loc => $byVal2loc, 
    byRef2loc => $byRef2loc, 
}); 

# Produces this output: 
Benchmark: timing 10000 iterations of byRef, byVal, direct... 
    byRef: 19 wallclock secs... 
    byVal: 15 wallclock secs... 
    direct: 4 wallclock secs... 

Benchmark: timing 10000 iterations of byRef2loc, byVal2loc... 
byRef2loc: 21 wallclock secs... 
byVal2loc: 119 wallclock secs... 

予想通り、直接的な方法が最も速かった。しかし、私が想像していた「データのコピー」に関連するペナルティはないことに驚きました。たとえ私がレコードの幅を異国的な割合(例えば10億文字)に増やしたとしても、バイ・バリューとバイ・リファレンスのベンチマークは基本的に同じでした。

引数をメソッドに渡すと、Perlはデータをコピーしないようです。私は、@ _のエイリアシング能力についてのさらなる反映が理にかなっていると思います。引数は値ではなく参照渡しに渡されます。

しかし、@_の参照をサブルーチン内のローカル変数に直接割り当てることができないため、これは参照渡しの制限された形式です。このような割り当ては、第2セットのベンチマークによって示されるように、データコピーをもたらす。

これを正しく理解していますか?

答えて

8

はい、割り当てはコピー;ただ引数を渡すだけではありません。しかし、Lexical::Aliasを使って、@ _の要素にレキシカルをエイリアスすることができます。この変形ベンチマークを行う示し、第3の速ようにかかわらず、$レコードの長さの一貫性基準を使用して、しかしとして:

use strict; 
use warnings; 
use Benchmark qw(timethese); 
use Lexical::Alias; 

my $RECORD = '0' x 5000000; 

my $byVal2loc = sub { my $v = ByVal2loc($RECORD, $_) for 0..999 }; 
my $byRef2loc = sub { my $v = ByRef2loc(\$RECORD, $_) for 0..999 }; 
my $byAlias2loc = sub { my $v = ByAlias2loc($RECORD, $_) for 0..999 }; 

sub ByVal2loc { my $arg = shift; return substr( $arg, $_[0], 1) } 
sub ByRef2loc { my $arg = shift; return substr($$arg, $_[0], 1) } 
sub ByAlias2loc { my $arg; alias($_[0], $arg); return substr($arg, $_[0], 1 ) } 

timethese($ARGV[0], { 
    byVal2loc => $byVal2loc, 
    byRef2loc => $byRef2loc, 
    byAlias2loc => $byAlias2loc, 
}); 

# output: 
Benchmark: running byAlias2loc, byRef2loc, byVal2loc for at least 3 CPU seconds... 
byAlias2loc: 3 wallclock secs (3.16 usr + 0.00 sys = 3.16 CPU) @ 430.70/s (n=1361) 
byRef2loc: 4 wallclock secs (3.24 usr + 0.00 sys = 3.24 CPU) @ 1329.63/s (n=4308) 
byVal2loc: 5 wallclock secs (4.95 usr + 0.01 sys = 4.96 CPU) @ 0.40/s (n=2) 
      (warning: too few iterations for a reliable count) 

(代わりにエイリアスヘルパー関数の直接使用alias_rがわずかに速くなる)

6

Perl 'sub'のIIRC配列は、すでに変数へのエイリアス(参照)のセットです。 $_[0]を変更すると、呼び出し元関数の変数に影響します。

#!/bin/perl -w 
use strict; 

sub x 
{ 
    print "x = $_[0]\n"; 
    $_[0] = "pinkerton"; 
    print "x = $_[0]\n"; 
} 

my $y = "abc"; 

print "y = $y\n"; 
x($y); 
print "y = $y\n"; 

出力は次のようになります。

y = abc 
x = abc 
x = pinkerton 
y = pinkerton 
+0

+1を。これは正解です。 –

+0

@Igor Krivokon:正解ですが、質問には少なくとも暗黙のうちにすでに述べられています。私は推測する "はい、これを正しく理解している。"答えとして何かが欠けている。 – ysth

0

あなたは意味のある名前@_の要素を与えたい場合は、あなたがData::Aliasを使用して、それらにエイリアスを作ることができるので、

use Data::Alias; 

sub foo { 
    alias my ($a, $b, $c) = @_; 
} 

あなたは配列やハッシュにエイリアシング同様のことを行うことができます。ハッシュ

alias my (%p) = @_; 

にエイリアシング実際に

alias my ($a, $b, @c) = @_; 
    alias my ($a, $b, %c) = @_; 

は、それが参照渡し、名前付きパラメータを提供するように特に強力です。ニース。

(データ::エイリアスは、字句::エイリアスの機能のスーパーセットを提供し、それはより一般的な目的と、より強力です。)

関連する問題