• ベストアンサー

サブルーチン内のサブルーチン定義について

サブルーチン内で定義したサブルーチンで、思い通りにならない挙動で困っています。 'test'を10万回繰り返す文字列の生成を行い、その文字列長を表示する関数を funcA とします。その生成過程では、自分の関数内で宣言した再帰関数 funcB を呼び出します。 #! /usr/local/bin/perl use strict; my $time0; for(my $i=0; $i<10; $i++){   $time0 = times();   &funcA();   print((times() - $time0). "\n"); # funcAに掛かった時間 } sub funcA {   my $buffer = '';   &funcB(1);   print length($buffer) . " : "; # $buffer の文字列長      sub funcB{     my $n = shift;     $buffer .= 'test';     return if($n==100000);     funcB($n+1);   } } この結果が、 400000 :3.063 0 :0.468 0 :0.594 0 :0.766 0 :0.859 0 :1.11 0 :1.187 0 :1.141 0 :1.343 0 :1.469 となり、初回以降 $buffer の長さが0となるのも不可解ですが、funcA の実行時間が増加していくのも理解できません. これを #! /usr/local/bin/perl use strict; my $time0; my $buffer; # 注1 $buffer をファイル内大域変数として宣言 for(my $i=0; $i<10; $i++){   $time0 = times();   &funcA();   print((times() - $time0). "\n"); } sub funcA {   $buffer = ''; # 注2 レキシカル変数宣言をやめた   &funcB(1);   print length($buffer) . " : ";      sub funcB{     my $n = shift;     $buffer .= 'test';     return if($n==100000);     funcB($n+1);   } } とすると、結果は 400004 :3.188 400004 :0.234 [以降、上にほぼ同じ] と文字列長は正しいものの,初回以降のfuncA実行時間が極端に減ります. 内部ではどういうことが起こっているのでしょうか.

  • ytse
  • お礼率71% (88/123)
  • Perl
  • 回答数3
  • ありがとう数3

質問者が選んだベストアンサー

  • ベストアンサー
  • osamuy
  • ベストアンサー率42% (1231/2878)
回答No.3

funcAの変数$bufferは、my宣言が実行されるたびに初期化されますが、funcBに現れている$bufferは、その外側のfuncAの1回目の呼び出し時に初期化されたものが保持されます。 ドキュメントperlsubの「永続的なプライベート変数」が参考になるかと。

参考URL:
http://www.kt.rim.or.jp/~kbk/perl5.005/perlsub.html
ytse
質問者

お礼

お返事遅れてしまい、もうしわけありません。 いただいた参考URL、大変勉強になりました。 ありがとうございます。

その他の回答 (2)

  • Ki4-U2
  • ベストアンサー率81% (364/446)
回答No.2

No.1 です。前半部の方では、やはり意図しない現象が起こっているようです。 funcB のreturn 文の前に、下の1行を加えてみました。 print length($buffer)." , " if($n==100000); 実行結果は下記のとおりです(Win XP の Active Perl 5.8.4) 400000 , 400000 : 5.908 800000 , 0 : 0.621 1200000 , 0 : 0.951 1600000 , 0 : 1.152 2000000 , 0 : 1.392 2400000 , 0 : 1.482 2800000 , 0 : 1.753 3200000 , 0 : 1.832 3600000 , 0 : 2.033 4000000 , 0 : 2.183 どうも、funcB の中の $buffer が、1回目終了時以降、funcB の外(funcA 内)の $buffer と別のものになっちゃってるみたいですね。 (ループを10万回でなく10回とかにしても同様でした。) 結局のところ不可解です。詳しい方の解説を待ちたいと思います。

ytse
質問者

お礼

上の方の参考URLの「永続的なプライベート変数 」が参考になりました. いろいろ試していただいてありがとうございますm(_ _)m perlは奥が深いですねぇ

  • Ki4-U2
  • ベストアンサー率81% (364/446)
回答No.1

とりあえず後半部についてですが。 1回目は、$bufferに使うメモリ(最大40万字分)を、文字数が増えるたびに割り当てし直すので、時間がかかる。 2回目以降は、1回目で最終的に割り当てられた40万字分をそのまま使うので、時間がかからない。 ということではないかと思います。 $buffer のファイル内大域変数宣言を、 my $buffer = ' ' x 400000; # 注1... として(あらかじめ40万字分割り当てておく)みると、1回目の結果が大幅に改善しました。 (それでも2回目以降の2~3倍の時間がかかるので、他にも理由はあるのでしょうけど)

ytse
質問者

補足

ありがとうございます. 後半についてはちょっとすっきりしました。 そういえば昔に#1さんのおっしゃることを、 http://mikeneko.creator.club.ne.jp/~lab/perl/tuning/#h1 のページで見ていました。でも、すっかり忘れていました. >my $buffer = ' ' x 400000; という表記方法があるんですね。2~3倍かかるのも謎ですが、 大変勉強になりました。 ありがとうございます。

関連するQ&A

  • サブルーティンの使い方。

    サブルーティンの理解を深めるために、 階乗の計算をサブルーティンで行うプログラムを作りました。 自作のプログラムについて質問を二つしたいと思います。 1.一応、計算は出来るのですが、 定型的でないというか、無駄が多いというか、 何か違う気がするのです。 どこか変なところはありませんでしょうか? 2.エラーメッセージをどこにいれたらいいのかわかりません。 数字以外、(例えば文字)が入力されれば1が出力されるようにはしたのですが、 「これは数字ではありません」のようなエラーメッセージを出したいのです。 この場合はどこにどのように記述すればいいですか? 色々試してみたのですが、思い通りに動きませんでした。 みなさま、知恵をお貸しください。 ------------------------------------------- use strict; print "数字を入力してください。\n"; chomp( my $number = <STDIN> ); my $k_number = kaijo($number); print "入力された数字の階乗は$k_numberです。\n"; sub kaijo { my $number = shift @_; return undef if $number < 0; return 1 if $number == 0; my $kaijo = 1; for(my $i = $number; $i>1; $i--) { $kaijo *= $i; } return $kaijo; }

  • 配列内定義サブルーチン呼び出し

    下記のような配列内定義サブルーチン呼び出しを作成し動作を確認しました。 次にソース2行目の「no strict;」を「use script;」に変更すると Bareword "sub1" not allowed while "strict subs" in use at refsub_OK.pl line 4. Bareword "sub2" not allowed while "strict subs" in use at refsub_OK.pl line 4. Bareword "sub3" not allowed while "strict subs" in use at refsub_OK.pl line 4. Execution of refsub_OK.pl aborted due to compilation errors. とエラーになります。 プログラムはやはり「use script;」を記述したいのですが、「use script;」を記述 したままでエラーにならない方法がありましたらおしえてください。 perlのバージョンは5.12.3、OSはWindowsXP SP3です。 よろしくお願いします。 ---ソースここから--- #!perl no strict; use warnings; my @ary = ( ("input1.txt", "save1.txt", sub1), ("input2.txt", "save2.txt", sub2), ("input3.txt", "save3.txt", sub3), ); while (@ary) { my $p1 = shift(@ary); my $p2 = shift(@ary); my $sub = shift(@ary); print "p1=[$p1] p2=[$p2] sub=[$sub]\n"; &$sub("$p1", "$p2"); } sub sub1 { my ($p1, $p2) = @_; print "sub1: [$p1] [$p2]\n"; } sub sub2 { my ($p1, $p2) = @_; print "sub2: [$p1] [$p2]\n"; } sub sub3 { my ($p1, $p2) = @_; print "sub3: [$p1] [$p2]\n"; } ---ソースここまで---

    • ベストアンサー
    • Perl
  • サブルーチンからサブルーチンを呼び出す

    #平均を求める,サブルーチンを使用 @dat=(1,2,3,4,5,6,7,8,9,10); $mean=&mean(@dat); print "mean="; print $mean; sub mean{ $sum=&sum(@dat); $n=@dat; $mean=$sum/$n; return($mean); } sub sum{ for($i=0;$i<$n;$i++){ $sum+=$dat[$i]; } return($sum); } Perlを勉強し始めたのですがサブルーチンの所で分からない所があるので教えていただけませんか? mean関数(Perlではmeanルーチンなどと呼ぶべきでしょうか?)の中でsum関数を呼び出してデータの合計を$sumに代入したいのですが,この場合だとうまく代入されていないようなのですが,何がよろしくないのでしょうか?

    • ベストアンサー
    • Perl
  • Perlのmyのサブルーチンの内外での宣言の違い

    Perlでmyを使う場合、サブルーチンの内外での宣言の違いについて知りたいです。 私はよくある変数をそのプログラム内全体で使う時(カウントするだけの$iみたいなものや、DBのクエリなど)は、同じ変数名をサブルーチン内で毎回宣言するのが嫌(個人的に同じことを重複するプログラムが好きではないという主義)なので、サブルーチンの外でmyを宣言するのですが、myとかはサブルーチン外で宣言するとどんな問題が生じる可能性があるのでしょうか? また毎回サブルーチン内で宣言するのと外部で宣言するのではどういった時に問題になるのでしょうか?もちろんサブルーチンの外でmy宣言した場合はサブルーチンにその変数が引き継がれることは知っています。 リファレンス部分で変わるような記述を見たことはあります。 さしあたって問題を感じでいないのです、ふと疑問に思ったのですが、どうもそれに書かれたソースが少ないので御存知の方がいればぜひ御教授願いたいです。 <例> my $hoge; sub hoge1 { $hoge = 1; } sub hoge2 { $hoge = 5; } と sub hoge1 { my $hoge = 1; } sub hoge2 { my $hoge = 5; }

    • ベストアンサー
    • Perl
  • サブルーチンにリファレンスを渡したのですが、うまくいきません。

    以下の2つのサブルーチン「printString1」「printString2」 のうち、「printString2」はエラーになります。 このような使い方はできないのでしょうか? my $string = "string"; printString1(\$string); printString2(\$string); sub printString1{ my $refString = $_[0]; print $$refString; } sub printString2{ $_[0]; print $$_[0]; }

    • ベストアンサー
    • Perl
  • サブルーチンへ渡した配列のリファレンスをデリファレンスするのが面倒なのですが。。

    MAIN: {   my @array = (1 .. 5);   print three(\@array);   exit; } sub three {   my $array = shift;   return $$array[2]; } のように、サブルーチンに配列リファレンスを渡したあと、$$array[2]のようにデリファレンスするのが面倒なのですが、 このとき$array[2]と書ける何か良い方法はないでしょうか? # 大きな配列を取り扱うので、リファレンスを使いたいんです。。 型グロブを使うことも考えたのですが、サブルーチン内でmyで宣言出来なくて、スコープ的にまずくなりそうなので断念しました; どなたかアドバイスしていただけると嬉しいです。。

    • ベストアンサー
    • Perl
  • perlでサブルーチンへの複数の配列渡し

    perlでサブルーチンに配列を渡しているのですが、 引数としている配列が1個の場合は問題ないのですが、 複数渡すと、第2引数以降が渡りません。 どの様にすれば上手くできますか。 例1 #!/usr/bin/perl @x1 = (14, 11, 5, 12, 8, 15); @x2 = (12, 10, 8, 9); print "main : @x1 \n"; &test(@x1); sub test { my (@arg1) = @_; print "sub : @arg1 \n"; } 結果 main : 14 11 5 12 8 15 sub : 14 11 5 12 8 15 例2 #!/usr/bin/perl @x1 = (14, 11, 5, 12, 8, 15); @x2 = (12, 10, 8, 9); print "main : @x1 \n"; print "main : @x2 \n"; &test(@x1, @x2); sub test { my (@arg1, @arg2) = @_; print "sub : @arg1 \n"; print "sub : @arg2 \n"; } 結果 main : 14 11 5 12 8 15 main : 12 10 8 9 sub : 14 11 5 12 8 15 12 10 8 9 sub : 引数1に全てが設定されて、引数2に設定されていない。

    • ベストアンサー
    • Perl
  • ハッシュリファレンスの未定義

    サブルーチン/ハッシュリ/ファレンスで悩んでいます。 my (@r); $r[1]{"A"} = "1-A"; # 代入 &s(\@r); print $r[1]{"A"},"\n"; # 参照 print $r[2]{"B"},"\n"; # 参照 サブルーチン側でできない。 # sub s() { my ($c)=@_; @$c[1]->{"A"} = "1111-AAAA"; # もちろん代入できる @$c[2]->{"B"} = "2-B"; # 代入 これができない(ハッシュリファレンスの未定義エラー) } サブルーチン側で新規ハッシュのところに代入ができないのですが どのようにすればいいのでしょうか。

    • ベストアンサー
    • Perl
  • 無名サブルーチンの疑問

    下記コードを実行しましたが$nameの文字が表示されません。 コードが間違っているのでしょうか? $string = sub ("さぶろう") { my($name) = @_; return("$nameです。\n"); }; print &$string;

    • ベストアンサー
    • Perl
  • Perlの変数宣言について

    PHPを毎日書いている者ですが、たまたまPerlのメールフォームをカスタマイズ しなければならなくなりました。 use strict宣言がある場合、変数はmyまたはlocalで宣言しなければいけないようですが、 サブルーチン内に sub form { ・・・ my ($form_value,$error_list) = @_; ・・・ print $error_list{'inquiry_type1'} としたところ、 Global symbol "%error_list" requires explicit package name at ・・・ と、宣言が無い場合に出るというエラーが出ました。 $error_listには、inquiry_type1は入っているはずなのですが、どのような 宣言をすればよいのでしょうか? よろしくお願いします。

    • ベストアンサー
    • Perl