Perlを使うとしばしば正規表現を使う。
僕はwhileでマッチング処理をぶん回す時、
while($x=~/(xxx)/cg){ # 〜処理〜 }
みたいな使い方をするときがある。
ある日ベンチマークとってみたら不自然なことに気がついた。
ある条件下でcオプションをつけていると、変な状態になる。
ある条件下というのは、
- グローバル変数を対象にしている
正確に言えばプライベート変数だけどもスコープの関係上、グローバルに見えるってだけだけど。
どっかで拾ってきた文字列の中に'100'がいくつあるか調べる処理。
コードとしては間違ってるけど、再現するので許してくだしあ。
case1 : cオプション付
case2 : cオプション無
コード
# '100'は 73個含まれている my $g_data = <<"_DATA_"; 1000001010110001100000101100110010000010 1110011010000010101001001000001011001001 0011001010010000011010011001000010010100 1000001011000101100011111001000110000010 1010100110000010111010101000001010111101 1001010110110110100011111100110110000010 1100110110000010111000001000001011001101 1000001011100010100100000110110010001010 1101010010000010110001011000001011001101 1001001111000111100000101101111110000010 1100100010000010101000101000001011001100 1000001011000101100011111001000110000010 1010110110000010110010001000000101000010 _DATA_ timethese(100000, { case1=> \&case1, case2=> \&case2 }); ######################################## sub case1{ my $i=0; while($g_data =~ /100/cg){ $i++; } return $i; } sub case2{ my $i=0; while($g_data =~ /100/g){ $i++; } return $i; }
ベンチマーク結果
Benchmark: timing 100000 iterations of case1, case2...
case1: 0 wallclock secs ( 0.03 usr + 0.00 sys = 0.03 CPU) @ 3125000.00/s (n=100000)
(warning: too few iterations for a reliable count)
case2: 2 wallclock secs ( 1.53 usr + 0.00 sys = 1.53 CPU) @ 65316.79/s (n=100000)
うはwwcオプションつけたほうがダンゼン早いwww
って思ったのだが、cつけたら50倍も早くなるとか流石におかしいだろ・・・
赤いヤツは通常の3倍といわれるものとは違うんだぞ・・・
実際に何回か表示してみるため、先ほどのベンチマーク処理を少し書き換えた。
単純にcase1を2回、case2を2回呼び出して画面上でみるだけ。
コード
my $g_data = <<"_DATA_"; 1000001010110001100000101100110010000010 1110011010000010101001001000001011001001 0011001010010000011010011001000010010100 1000001011000101100011111001000110000010 1010100110000010111010101000001010111101 1001010110110110100011111100110110000010 1100110110000010111000001000001011001101 1000001011100010100100000110110010001010 1101010010000010110001011000001011001101 1001001111000111100000101101111110000010 1100100010000010101000101000001011001100 1000001011000101100011111001000110000010 1010110110000010110010001000000101000010 _DATA_ print "1: case1 : ", case1() , "\n"; print "2: case1 : ", case1() , "\n"; print "3: case2 : ", case2() , "\n"; print "4: case2 : ", case2() , "\n"; ######################################## sub case1{ my $i=0; while($g_data =~ /100/cg){ $i++; } return $i; } sub case2{ my $i=0; while($g_data =~ /100/g){ $i++; } return $i; }
結果
1: case1 : 73
2: case1 : 0
3: case2 : 0
4: case2 : 73
期待する結果は73なのだが、明らかに0を返している部分がある。
それぞれの関数は$g_dataにアクセスこそするが、書き換えは一切行っていない。
なので、いずれも同じ結果になるはずなのだが、結果が異なるように見える。
どういうことなの・・・
cオプションをはずせばcase1もcase2も全く同じ関数になるので、結果は同じになる。
だいぶ悩んで出した推測。
gオプションを使うと連続マッチを行うので、
次のマッチングを行うとき、マッチした後ろの文字列を参照するための位置情報が必要になる。
例えば'100100100'という文字列があって'100'が何個あるか調べるとして、
'"100"100100' -> '100"100"100' -> '100100"100"'
と推移してほしいのだから、「どこまでマッチングをしたか」を覚える必要がある。
ここではそれを「位置情報」としている。
おそらく、cオプションは正規表現で使う位置情報を初期化しないで再利用するのでは?と推測。
つまり、正規表現を終えてもその位置情報をクリアしない、みたいな。
んで、case2はなぜ1度だけ影響受けるのかというと、
case1と同じグローバル変数$g_dataを扱っているため、case1の直後にcase2を呼び出した1回だけ、その影響を受けている、と。
このことから、位置情報の記憶は変数ごとに行われているのでは?
cがついていないcase2は1回目の正規表現処理が終わった時点で情報が破棄されるため、
2度目は期待通りの結果を得る事ができている。
推測が正しければ、上の現象の説明がつく。
また、位置情報の記憶が変数ごとに行われていても、次のマッチング時に変数に変化があった場合は、その位置情報は何の役にもたたない。なので、リセットされるはずである。
同じ内容の変数を2つ用意。
case1とcase2はそれぞれの変数を使う。さらにcase1にのみcオプションをつける。他は同じ処理。
何度か呼び出したり、値の変更を行ってみて反応を見る。
コード
use Benchmark; use strict; my $g_data = <<"_DATA_"; 1000001010110001100000101100110010000010 1110011010000010101001001000001011001001 0011001010010000011010011001000010010100 1000001011000101100011111001000110000010 1010100110000010111010101000001010111101 1001010110110110100011111100110110000010 1100110110000010111000001000001011001101 1000001011100010100100000110110010001010 1101010010000010110001011000001011001101 1001001111000111100000101101111110000010 1100100010000010101000101000001011001100 1000001011000101100011111001000110000010 1010110110000010110010001000000101000010 _DATA_ my $g_data2 = $g_data; # コピー #################################### sub case1{ my $i=0; while($g_data =~ /100/cg){ $i++; } return $i; } sub case2{ my $i=0; while($g_data2 =~ /100/g){ $i++; } return $i; } print "1: case1 :", case1() , "\n"; print "2: case1 :", case1() , "\n"; print "3: case2 :", case2() , "\n"; print "4: case2 :", case2() , "\n"; print "5: case1 :", case1() , "\n"; print "6: case1 :", case1() , "\n"; $g_data .= ''; # 空文字列を後方へ連結 print "7: case1 :", case1() , "\n"; print "8: case1 :", case1() , "\n";
結果
1: case1 :73
2: case1 :0
3: case2 :73
4: case2 :73
5: case1 :0
6: case1 :0
7: case1 :73
8: case1 :0
先ほどのコードではcase1の直後のcase2は影響を受けていたが、
使う変数を変えた今回のコードではそのような影響を受けていない。
case2は何度呼び出しても同じ結果なので、完全に独立していることがわかる。
case2の直後にcase1を2度呼び出してもやはり、0を返したため、位置情報はリセットされていない。
このことから、変数ごとに位置情報を記憶している説はほぼ合っているといえる。
また、case1で使っている変数に空文字列を連結させた(=実際に変化はない)ところ、
直後のcase1の呼び出しで本来の期待していた結果である73を返したが、やはり2度目では0を返してきた。
同じ変数で違う正規表現のマッチングを行った時どうなるのか。
マッチング条件の100を1010に置き換えただけのcase3を用意して以下のコードを試した
コード
use Benchmark; use strict; my $g_data = <<"_DATA_"; 1000001010110001100000101100110010000010 1110011010000010101001001000001011001001 0011001010010000011010011001000010010100 1000001011000101100011111001000110000010 1010100110000010111010101000001010111101 1001010110110110100011111100110110000010 1100110110000010111000001000001011001101 1000001011100010100100000110110010001010 1101010010000010110001011000001011001101 1001001111000111100000101101111110000010 1100100010000010101000101000001011001100 1000001011000101100011111001000110000010 1010110110000010110010001000000101000010 _DATA_ sub case1{ my $i=0; while($g_data =~ /100/cg){ $i++; } return $i; } # case1のマッチング条件の100を1010に置き換えただけ sub case3{ my $i=0; while($g_data =~ /1010/cg){ $i++; } return $i; } print "1: case1 :", case1() , "\n"; print "2: case1 :", case1() , "\n"; print "3: case3 :", case3() , "\n"; print "4: case3 :", case3() , "\n";
結論
- 正規表現のオプション c は連続マッチを行う時に正規表現の位置情報を変数ごとに記憶するオプション
- 条件が変わっても同じ変数を参照している以上は、位置情報は引き継がれる。
- 変数に代入が行われると、この情報はリセットされる。
- cオプションを理解しないで使うとトラブルの原因になりそう。
PerlでCGIを勉強するためにかなり前に買った、翔○社のPerl/CGI辞典には、
オプション 意味
c (gといっしょに使い)継続検索を行う
って書いてあったけど、説明不足だなぁ。
書いていくうちに疑問が沸いて調べて書いてを繰り返すからこんなまとまらない文章に・・・
@追記
"詳説 正規表現" の 7.5.4.4 の項目にて。 /gcオプションの例が記述されていた。
やはり、cをつけると位置情報(pos)が、マッチング失敗時にもリセットされなくなるという旨が書かれていた。
"詳説 正規表現" がほしくなった。書店に行く事があったら買おうかな。