0512345678910111213141516171819202122232425262728293007

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
【 --/--/-- (--) 】 スポンサー広告 | TB(-) | CM(-)

正規表現の自主トレ、日付編

次は日付型にするか。
まずは8桁の整数でYYYYMMDDという形で表現する場合。
YYYY、MM、DDそれぞれに当てはまる数の整合性(月の大小(31日か30日))、うるう年の2月の日付など、正規表現でやろうとしたらキリがないものについては、正規表現の外でやる事にする。
つまり、
・YYYY、MM、DDの各部分が、それぞれ4、2、2文字の数字だけで構成される文字列であるという事の確認
・後でそれら項目をチェックなどの目的で使えるよう、何らかの変数に格納するという処理
について性能を比べることにする。

\dの方が[0-9]より速いという先日わかった事をふまえ、[0-9]を使った比較はしない。
月の部分は01-12なので、0[1-9]|1[0-2]とか、いろいろ細かいことをこだわり出すと、大変になってしまうし、複雑な正規表現は性能が悪くなり本末転倒。


(\d{4})-(\d{2})-(\d{2})
(\d\d\d\d)-(\d\d)-(\d\d)

くらいの簡単なチェックで済ませる。で、マッチした年・月・日部分の値のチェックは後続処理で行う形で。


Perlに詳しい人が言ってた事を思い出すと、正規表現でやらずにできる事は、正規表現以外を使った方が速いという事だった。
だとすると、パターンマッチ以外に変数として値を取得するためにも使ってるカッコを外してしまうというのはどうだろう。


超シンプルに、
/^\d{4}-\d{2}-\d{2}$/

のマッチが成功したら、

$year = substr ($str, 0, 3);
$month = substr ($str, 4, 2);
$day = substr ($str, 6, 2);
で取ってくるだけ。


$ cat bm_regex_ansidate.pl
#!/usr/bin/perl
use warnings;
use strict;

use Benchmark qw(:all);

our $line;
our ($year, $month, $day);
our $D = '-';
our $filename = "./ansidate.1m.txt";
our $regex_d_chu_kakko = qr!^(\d{4})$D(\d{2})$D(\d{2})$!;
our $regex_d_dddd = qr!^(\d\d\d\d)$D(\d\d)$D(\d\d)$!;
our $regex_simple_d_nocapt = qr!^\d{4}$D\d{2}$D\d{2}$!;

sub setup {
open (IN_FILE, $filename);
}
sub teardown {
close (IN_FILE);
}

timethese (100,
{
RegEx_d_dddd => sub {
setup;
while ($line=<IN_FILE>) { chomp $line; ($year,$month,$day)=($line =~ /$regex_d_dddd/); };
teardown;
},
RegEx_d_chu_kakko => sub {
setup;
while ($line=<IN_FILE>) { chomp $line; ($year,$month,$day)=($line =~ /$regex_d_chu_kakko/); };
teardown;
},
RegEx_simple_d_nocapt => sub {
setup;
while ($line=<IN_FILE>) { chomp $line;
if ($line =~ /$regex_simple_d_nocapt/) {
$year = substr($line,0,4);
$month = substr($line,5,2);
$day = substr($line,8,2);
}
};
teardown;
},
}
);



$ perl bm_regex_ansidate.pl
Benchmark: timing 100 iterations of RegEx_d_chu_kakko, RegEx_d_dddd, RegEx_simple_d_nocapt...
RegEx_d_chu_kakko: 82 wallclock secs (82.18 usr + 0.25 sys = 82.43 CPU) @ 1.21/s (n=100)
RegEx_d_dddd: 79 wallclock secs (78.85 usr + 0.23 sys = 79.08 CPU) @ 1.26/s (n=100)
RegEx_simple_d_nocapt: 67 wallclock secs (66.24 usr + 0.22 sys = 66.46 CPU) @ 1.50/s (n=100)


キャプチャーなしでめちゃめちゃ速くなった。これで決定。

区切り文字のない日付(yyyymmdd形式)も、同じような最適化ができる。
正規表現も、もっと簡単に、
'/^\d{8}$/'
と書くだけ。


スポンサーサイト
【 2014/10/06 (Mon) 】 OS Linuxコマンド(編集用) | TB(0) | CM(0)

正規表現の自主トレ:ASCII文字列

正規表現(Regular Expression)って、RegExって略すらしいけど、なんかカッコイイね。日曜朝の子供向け番組で、変身したり光線出したりしてるシーンで叫んでも全然違和感ない。w
「レグエーーーックス!!」


さて本題。カンマや小数点のある場合の評価方法は、ちょっとマッチする正規表現が面倒なので、まずは他の文字列のチェックから試すか。

ASCII文字(UTF-8 Unicodeでない文字列)のチェック。

[:ASCII:]という文字クラスと、0x01-0x7fという範囲にバイト値が収まっているかチェックする方法がある。

もちろん、マッチしちゃいけない文字が一文字でもあったらダメ、というチェック方法がここでもつかえて、性能はこんな感じ。


$ cat bm_regex_isascii.pl
#!/usr/bin/perl
use warnings;
use strict;

use Benchmark qw(:all);

our $line;
our $filename = "./int.1m.txt";
our $regex_posix = qr!^[[:ascii:]]+$!;
our $regex_hex = qr!^[\x01-\x7f]+$!;
our $regex_not_posix = qr![^[:ascii:]]!;

sub setup {
open (IN_FILE, $filename);
}
sub teardown {
close (IN_FILE);
}

timethese (100,
{
RegEx_posix => sub {
setup;
while ($line=<IN_FILE>) { chomp $line; $line =~ /$regex_posix/; };
teardown;
},
Not_RegEx_hex => sub {
setup;
while ($line=<IN_FILE>) { chomp $line; $line =~ /$regex_hex/; };
teardown;
},
RegEx_not_posix => sub {
setup;
while ($line=<IN_FILE>) { chomp $line; $line !~ /$regex_not_posix/; };
teardown;
},
}
);


$ perl bm_regex_isascii.pl
Useless use of negative pattern binding (!~) in void context at bm_regex_isascii.pl line 34.
Benchmark: timing 100 iterations of Not_RegEx_hex, RegEx_not_posix, RegEx_posix...
Not_RegEx_hex: 74 wallclock secs (74.04 usr + 0.23 sys = 74.27 CPU) @ 1.35/s (n=100)
RegEx_not_posix: 57 wallclock secs (56.43 usr + 0.20 sys = 56.63 CPU) @ 1.77/s (n=100)
RegEx_posix: 73 wallclock secs (72.92 usr + 0.23 sys = 73.15 CPU) @ 1.37/s (n=100)


これは「...以外」の書き方が非常に大きく貢献した。
16進数で01~7fまでというクラスを定義したRegEx_hexと、[:ascii:]というPosix事前定義クラスを使ったRegEx_posixの処理時間はほとんど変わらなかったのに、Posixクラスに該当しない文字があったらダメというチェック方式のRegEx_not_posixは、大差をつけて短く終わった。
もしかしたら、ASCII文字かどうか調べるのはチェックする文字数が多いから?
そんなわけないよな。$char=\x01 or $char=\x02 or...みたいな調べ方してるとは思えないし。普通に考えたら、単純に不等号で範囲チェックだよな。なぜだろう。


 
【 2014/10/05 (Sun) 】 OS Linuxコマンド(編集用) | TB(0) | CM(0)

Perlの正規表現、簡単な否定形で性能比較してみる

いちばん簡単そうな、数字だけで書かれた整数のチェック。(カンマなし、小数点以下もなし)
これをチェックする正規表現を、PerlのBenchmarksモジュールを使い、性能比較してみる。
数値を表す文字列だけで、他に前後のスペースなどもない前提。

で、先日の、数値の表し方を[0-9]と\dで比べてみた内容の続き。


our $regex_d = qr!^\d+$!;
our $regex_0_9 = qr!^[0-9]+$!;


RegEx_0_9: 69 wallclock secs (68.76 usr + 0.19 sys = 68.95 CPU) @ 1.45/s (n=100)
RegEx_d: 63 wallclock secs (63.55 usr + 0.20 sys = 63.75 CPU) @ 1.57/s (n=100)


\dの方が微妙に速いみたいだ。
でも、これはPerlの話。Perl以外では違うかもしれないし、\dを使えない処理系もあるし。

先日の正規表現チェックに加え、もっと効率的なチェック方法がないか考えてみた。

\d(もしくは[0-9])ばかりずっと続くものが数値を表す文字列(カンマなし、小数点以下なし)

という考え方そのものを見直すと、

\d以外の文字が1文字でも入ってたら数値でない文字列

という考え方もできる。

正規表現はこんな感じ。

our $regex_D = qr!\D!;

ベンチマーク実行するところはこんな感じで追加。正規表現を「あって欲しくない文字」に変えたので、マッチ演算子を、=~から!~の否定形に変えている。

Not_RegEx_D => sub {
setup;
while ($line=<IN_FILE>) { chomp $line; $line !~ $regex_D; };
teardown;
},


実行結果。ワーニングとして、何も受けるものがない箇所で否定形にしても意味ないよと言ってるが、気にしない。

Useless use of negative pattern binding (!~) in void context at ./bm_regex_isdigit2.pl line 29.
Benchmark: timing 100 iterations of Not_RegEx_D, RegEx_0_9, RegEx_d...
Not_RegEx_D: 60 wallclock secs (59.76 usr + 0.24 sys = 60.00 CPU) @ 1.67/s (n=100)
RegEx_0_9: 64 wallclock secs (63.95 usr + 0.17 sys = 64.12 CPU) @ 1.56/s (n=100)
RegEx_d: 61 wallclock secs (60.69 usr + 0.19 sys = 60.88 CPU) @ 1.64/s (n=100)


んー、もっと大きく変わるかと思ったけど、対して変わらなかったな。まぁ、否定形で考えた方がわかりやすい場合は、そのように書ける、という事で。


 
【 2014/10/01 (Wed) 】 OS Linuxコマンド(編集用) | TB(0) | CM(0)

Perl Benchmark モジュールを使ってみる

Perlは十数年前にCGIが流行ってた頃に勉強した程度で、実務で使ったことは全くない。仕事以外のネットの利用についても、すぐにブログなどのSNSの仕組みが流行り、自分でいちいちスクリプト言語やHTMLを書く必要もほとんどなくなったし。

で、存在だけは知ってたけど、よくわからなくて使わなかった、Benchmarkモジュール。これを使ってみることにしよう。
Perlで書ける処理を、いくつか書き方を変えて性能の影響を見たい時に使える。今回みたいな、正規表現の比較なんて時に効果的か。

なぜそういう文法でそういう動きになるかって気にしてたらいつまでたっても書けないけど、ネットで転がってるサンプルをもとに、正規表現マッチをひたすら繰り返すテストを書いてみる。


$ cat bm_regex_test.pl
#!/usr/bin/perl
use warnings;
use strict;

use Benchmark qw(:all);

our $line;
our $filename = "./int.1m.txt";
our $regex_d = qr!^\d+$!;
our $regex_0_9 = qr!^[0-9]+$!;

sub setup {
open (IN_FILE, $filename);
}
sub teardown {
close (IN_FILE);
}

timethese (1,
{
RegEx_d => sub {
setup;
while ($line=<IN_FILE>) { chomp $line; $line =~ /$regex_d/; };
teardown;
},
RegEx_0_9 => sub {
setup;
while ($line=<IN_FILE>) { chomp $line; $line =~ /$regex_0_9/; };
teardown;
},
}
);



$ perl bm_regex_test.pl
Benchmark: timing 1 iterations of RegEx_0_9, RegEx_d...
RegEx_0_9: 1 wallclock secs ( 0.71 usr + 0.00 sys = 0.71 CPU) @ 1.41/s (n=1)
(warning: too few iterations for a reliable count)
RegEx_d: 0 wallclock secs ( 0.62 usr + 0.01 sys = 0.63 CPU) @ 1.59/s (n=1)
(warning: too few iterations for a reliable count)


ありゃ。短すぎると警告が出るみたい。実行回数を1回でなく100回にしてみた。(100回x100万件=1億回)


$ perl bm_regex_test.pl
Benchmark: timing 100 iterations of RegEx_0_9, RegEx_d...
RegEx_0_9: 69 wallclock secs (68.76 usr + 0.19 sys = 68.95 CPU) @ 1.45/s (n=100)
RegEx_d: 63 wallclock secs (63.55 usr + 0.20 sys = 63.75 CPU) @ 1.57/s (n=100)


やっとマトモな比較ができそうな時間になってきた。
ファイルのopen, closeが1回でなく100回発生しているけど、まぁそれくらいは大した事なさそうだからいいか。

open, close処理をsetup, teardownって名前にしてるのは、xUnitっぽく気取ってみただけ。別にAssertion使わないけど。w


 
【 2014/09/29 (Mon) 】 OS Linuxコマンド(編集用) | TB(0) | CM(0)

正規表現自主トレ用のデータ作成

Hyper-Vの仮想CentOSマシンも、VMwareでやった時と同じネットワークの設定方法で片付いた。ブート時に起動される設定スクリプトを編集するだけ。
/etc/sysconfig/network-scripts/ifcfg-eth0ファイルがDHCPとして作られてしまってるのを固定IPアドレスにして直し、/etc/resplv.confでDNSを8.8.8.8 (GoogleのパブリックDNS)にするだけ。


ネットワークが使えるようになったので、yumでPerlとwgetをインストール。
で、使えるようになったから、tpc-hのデータ生成プログラムを取得してコンパイル。

夜間流しっ放しにしないといけないような性能検証を自分の家でやるわけにはいかないから、そこまで大規模でなくてもいいか。
シンプルな感じで性能への影響がわかりやすくしなきゃ。誰かに売る何かを作るわけじゃないし。
ある程度まとまった分量って、100万件くらいあればいいか。
仮想マシンで実行するし、他のプロセス・タスクも動いてるので、応答時間でなくCPU時間で見なきゃいけないな。


いちばん行数が多く作られるデータはlineitem。これを使うことにしよう。
スケール要素として1を指定すると600万、これを削って100万件にする。


# wc lineitem.1m.tbl
1000000 5912915 132538292 lineitem.1m.tbl
# head -2 lineitem.1m.tbl
1|310378689|15378705|1|17|29786.89|0.04|0.02|N|O|1996-03-13|1996-02-12|1996-03-22|DELIVER IN PERSON|TRUCK|egular courts above the|
1|134618160|14618161|2|36|38571.48|0.09|0.06|N|O|1996-04-12|1996-02-28|1996-04-20|TAKE BACK RETURN|MAIL|ly final dependencies: slyly bold |


個別データ型(整数、小数、日付、文字列)のフィールドを切り出し、別ファイルにする。
これは面倒なので、awkで一発で切り出す。


# head ansidate.1m.txt
1996-03-13
1996-04-12
1996-01-29
1996-04-21
1996-03-30
1996-01-30
1997-01-28
1994-02-02
1993-11-09
1994-01-16
# ls -l *.txt
-rw-r----- 2 root root 11000000 Sep 27 17:26 ansidate.1m.txt
-rw-r----- 2 root root 11110926 Sep 27 14:44 dec.trimmed.1m.txt
-rw-r----- 2 root root 10657083 Sep 27 14:25 dec.untrimmed.1m.txt
-rw-r----- 2 root root 9722471 Sep 27 17:27 int.1m.txt
-rw-r----- 2 root root 9000000 Sep 27 17:31 integerdate.1m.txt
-rw-r----- 2 root root 13000000 Sep 27 14:12 quoted.1m.txt
-rw-r----- 2 root root 21500000 Sep 27 17:52 timestamp.1m.txt
【 2014/09/28 (Sun) 】 OS Linuxコマンド(編集用) | TB(0) | CM(0)
プロフィール

Ed U Song

Author:Ed U Song
社内ノマドなエンジニア。
仕事で触れる機会のないものを自宅環境作って実験。

スポンサーリンク
最新コメント
最新トラックバック
検索フォーム


                                         
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。