2009年1月31日

リファクタリング

じゃぁ、相関とかを求めるかね。」で公開した、calcr2.plに新たなる任務が与えられた。

calcr2.pl は「与えられたTSVファイルの2列目のデータと、2列目以降最後までのデータとの相関を求めることができた。これは「ヒント」となるべき兆候があり、それを2列目に置く事ができたから可能だった方法だ。
新しいミッションはこうだ:

データを与えるTSVファイルのフォーマットは従来と変わらない。
このデータとは別に2つのファイル x と y を与える。x,yそれぞれにラベル名が入っているので、x と yの全組み合わせについてピアソン積率相関係数を求めなさい。

今度はノーヒント、というわけだ。

とりあえず、x と y というファイルのフォーマットを決める。と言っても、ラベル名を列挙するだけなのだから、1行1ラベル名にするだけだ。こんな感じ:
"LogicalDisk(C:)\% Disk Read Time"
"LogicalDisk(C:)\% Disk Time"
"LogicalDisk(C:)\% Disk Write Time"
"LogicalDisk(C:)\% Free Space"
"LogicalDisk(C:)\% Idle Time"

ラベル名の前後に " をつけてあるのは、それら全体で一塊、ということを示しているのにすぎない。つーかそもそもこれ自体もとのデータファイルの第1行目を分割して作っているので、実は " をはずすのが面倒だった、という事に過ぎない。

呼び出しのルールはこうしよう:
$ correlation.pl <x> <y> <data>

xのリスト、yのリスト、そして最後にデータだ。データファイルは相変わらずWindows用の perfmon.exe の出力である .blg ファイルを relog.exe で TSV フォーマットに変換した結果、とする。

「ふはははは、そんなもの朝飯前だよ、明智君」

とばかりにちょろっと直してみた結果がこれだ。
#! /usr/bin/perl
#
# correlation.pl <x tagname> <y tagname> <datafile tsv from exe relog>
#

use Math::NumberCruncher;
$ref = Math::NumberCruncher->new();

use open IN => ":utf8", OUT => ":utf8";


open( COMPARE_X, $ARGV[0] ) or die "unable to open $ARGV[0]";
open( COMPARE_Y, $ARGV[1] ) or die "unable to open $ARGV[1]";
open( DATAFILE, $ARGV[2] ) or die "unable to open $ARGV[2]";

# First, read file to get the list we need to calculate correlation with.
our $i = 0;
our @x_tagname = ();
while (<COMPARE_X>) {
$_ =~ s/^\"//;
$_ =~ s/\"\n$//;
$x_tagname[$i] = $_;
$i++;
}
close COMPARE_X;

our $i = 0;
our @y_tagname = ();
while (<COMPARE_Y>) {
$_ =~ s/^\"//;
$_ =~ s/\"\n$//;
$y_tagname[$i] = $_;
$i++;
}
close COMPARE_Y;


# Read first line of <datafile>.
# This should be the list of TAG-NAME.
$line = <DATAFILE>;
@tagname = split /\t/, $line;
for ( $i = 0; $i < scalar( @tagname ); $i++ ) {
my $val;
$val = $tagname[$i];
$val =~ s/^\s*"//;
$val =~ s/"\s*$//;
$tagname[$i] = $val;
}

our $line = 0;
while (<DATAFILE>) {
my @oneline = split /\t/, $_;
for ( $i = 0; $i < scalar( @oneline ); $i++ ) {
my $val;
$val = $oneline[$i];
$val =~ s/^\s*"//;
$val =~ s/"\s*$//;
$data[$i][$line] = $val;
}
$line++;
}
close DATAFILE;

#######
# So we read all the necessary data.
#
# Now calculate colerration.
#
#######

# First, check where each x_tagname is.
XTAG_LOOP: for ( my $x = 0; $x < scalar( @x_tagname ); $x++ ) {
my $localxval = quotemeta $x_tagname[$x];
for ( my $y = 0; $y < scalar( @tagname ); $y++ ) {
if ( $tagname[$y] =~ m/$localxval/ ) {
$x_tags[$x] = $y;
next XTAG_LOOP;
}
}
if (( $x <= scalar( @x_tagname ))&&( !defined( $x_tags[$x] ) )) {
die "Unable to find $x th tag \"$x_tagname[$x]\" as tag name for giving list.";
}
}

# Second, check where each y_tagname is.
YTAG_LOOP: for ( my $y = 0; $y <= scalar( @y_tagname ); $y++ ) {
my $localyval = $y_tagname[$y];
for ( my $x = 0; $x < scalar( @tagname ); $x++ ) {
if ( $tagname[$x] =~ m/$localxval/ ) {
$y_tags[$y] = $x;
next YTAG_LOOP;
}
}
if (( $y < scalar( @y_tagname ))&&( !defined( $y_tags[$y] ) )) {
die "Unable to find $y th tag \"$y_tagname[$y]\" as tag name for giving list.";
}
}

# print the tag line.
print "\"\"\t";
for ( my $x = 0; $x < scalar( @x_tagname ); $x++ ) {
print "\"$x_tagname[$x]\"\t\"\"\t\"\"\t\"\"";
if ( $x < scalar( @x_tagname ) - 1 ) {
print "\t";
} else {
print "\n";
}
}

# print the 2nd tag line.
print "\"\"\t";
for ( my $x = 0; $x < scalar( @x_tagname ); $x++ ) {
print "\"r\"\t\"a\"\t\"b\"\t\"r2\"";
if ( $x < scalar( @x_tagname ) - 1 ) {
print "\t";
} else {
print "\n";
}
}

for ( my $y = 0; $y < scalar( @y_tags ); $y++ ) {
print "$y_tagname[$y]\t";
for ( my $x = 0; $x < scalar( @x_tags ); $x++ ) {
my $r = Math::NumberCruncher::Correlation( \@{$data[$y_tags[$y]]}, \@{$data[$x_tags[$x]]} );
my ($a, $b) = Math::NumberCruncher::BestFit( \@{$data[$y_tags[$y]]}, \@{$data[$x_tags[$x]]} );
my $r2 = $r * $r;

print "\"$r\"\t\"$a\"\t\"$b\"\t\"$r2\"";
if ( $x < scalar( @x_tags ) - 1 ) {
print "\t";
} else {
print "\n";
}
}
}


実行してみる………… zzzzzzz …………

おーそーいーーーーーー!!!
救いがたいほどに遅い。あわててデータを見てみる。

ぶっっ!!!


調べる相手だけで、12791項目もある…今までが3500とかのレベルだったから4倍近い。何も考えずに相関を総当りで取ったら、今までの16倍時間がかかる。只でさえ遅いのに…。

こうしてスピードアップを余儀なくされたのだが、そもそもそういう小細工をするには、ちょっとコードが汚すぎるし、外部ライブラリに依存しすぎている。というわけで、リファクタリングを開始することにした。

この物語は、そのリファクタリングの記録である…というわけで続く。