2009年5月6日

RAWデータを操れるのか…UFRaw…こりゃ便利だ

デジカメとして ニコンのD70 と PanasonicのLUMIX-G1 を持っている。で、この2つのカメラ、どちらも RAW フォーマットをサポートしている。

サポートしているのはよいのだが、これを読み込むためのソフトがPhotoshop等の有料ソフトしかなかった。メーカーごとにフォーマットが違い、JPEGのように標準化されていないからだ。そのため、今まではJPEGで撮影していたのだが…

UFRawというソフトがある事を知った。各社のRawファイルを読み込み、ある程度の色調調整などは自分でもできるソフト。
最大のポイントはGIMPのプラグインとしても動作するので、RAWデータをGIMPで操作して、結果を別のフォーマットなどにも保存できることだろう。

G1はRawとJpegを同時保存できるので、それで見ていると、たまにバグって色調がおかしくなっている場合があるが、それ以外は快調。

今までは操作もできなければプレビューすらできないRawモードは避けてきたんだけれど、これからは使ってみようかと思う。

2009年4月11日

複数のクレジットカードを持つのは効果的なのか?

いま、あちこちの銀行の定期預金とかの利率を調べている。金額的には100万円未満の利率。期間的には3年以内。円建てに限定する。

一例としては、こんな感じ。

みずほ銀行 普通預金金利 0.040%
定期預金 0.15% - 0.25%
貯蓄預金 0.07%
三菱東京UFJ銀行 普通預金 年0.040%
スーパー普通預金 年0.050%
スーパー定期 年0.150% - 年0.250%
自由金利型定期預金(大口定期) 年0.150% - 年0.350%
貯蓄預金 年0.040% - 年0.050%


見ての通り、出し入れ自由だと 0.04% - 0.05%, 出し入れ不自由だと 0.15% - 0.35% と言った所。3年預けたとして、利子は

0.04%1.0004^3 = 1.00120048 0.12%
0.05%1.0005^3 = 1.00150075 0.15%
0.15%1.0015^3 = 1.00450675 0.45%
0.35%1.0035^3 = 1.010536791.05%

と言った所。



なぜこんなことを計算しているかと言うと、実はこの本を読んでいるから。

この「新・資本論」という本の中に、こういう記述があるのだ。

日本でお金を稼いだとき、私はそれを三つに分けることを推奨する。三分の一をドルに、三分の一をユーロに、そして残りを円建ての金融商品に転換するのである。(p.142)

為替相場が乱高下しているときに「三分の一」にするのはどうかと思うが、これは結構面白いポイントをついていると思う。つまりこういうことを考えるのだ:


普段から銀行口座を3種類持つ。ドル建て、円建て、ユーロ建ての3つ。
そして、それぞれに対してクレジットカードを1つづつ作る。
収入を得たら、日常に使うお金は円建てに、残りを為替相場に応じて、適当に分配する。


何か買い物をする場合は、そのときの為替相場を考慮して、もっとも効果的な口座から引き落とされるカードを利用する。



例えば円高ドル安ユーロ安の場合は、すべての決済を円口座カードで済ませる。また、ドルやユーロ建ての講座に多い目にお金を預ける。

逆に円安ドル高になったら、決済をドル口座カードで済ませる。この場合、円高ドル安だった頃に買ったドルで決済をすることになるので、為替差額がそのまま利子として働く。

このような運用をした場合と、すべてを円建てで運用して定期預金などを使った場合と、どっちがお徳か考えてみよう、というわけ。話を簡単にするために、期間は3年限定とする。また、為替相場は現在の1ドル100円を基準として、1ドル最低95円、最高105円の範囲で動くとしよう。ユーロは考えない。とりあえず面倒だから。最後に、カード破産はしない、という前提も必要だね。

ようするに、1ドル95-100円の範囲の場合、ドル建てを 60%, 円建てを40%で、100-105円の場合、ドル建てを40%, 円建てを60%で普通預金に入れる事を考える。どうせ利子は付かないに等しいので、考慮しない。

一方で、消費する場合は1ドル95-100円の範囲の場合は円建てカードを優先、100-105円の場合はドル建てカードを優先で使うことにする。不足したらもう一方のカードで支払う。

これをすべて円建てで見ると、円建て口座の方は入れた金額が貯蓄金額だ。一方でドル建て口座の方は、1ドル97.5円平均で入金して、1ドル102.5円平均で出金することになる。利子としては 5.12% ついているのに等しい。円とドル半々で利用するので、実質利子はこの半分、2.5%…さらに不足分が出た場合のソンを考慮すると… 2%ぐらいか。



なんだ。

各通貨の銀行預金を作り、それぞれを引き落とし先とするクレジットカードを作り、為替相場に応じてどのカードで引き落とすかを変える
という比較的リスクの少ない戦略を使っただけでも、銀行の定期預金よりも利率が良い結果になるじゃないか。

2009年3月7日

解答を書いておく -3- 部分解の2

request2


meminfo.awk が存在する、と言う前提からはじめる。

SLAB=$(( awk -f meminfo.awk /proc/meminfo >> $LOGFILE ) 2>&1 )

で $SLAB に現在の /proc/meminfo 中のSLABの値が取得できているとしよう。他に次のような値を bash スクリプトレベルで取得、定義できているとする。

# We only have 876Mbytes of memory space for kernel.
KERNELMEMORY=$( expr 876*1024 )
PREVIOUSSTATE="GREEN"

# 358809kbyte is 40% of 876*1024Kbytes
YELLOWBORDERSIZE=358809

# or define this.
# YELLOWLIMIT=0.4

# 627916kbyte is 70% of 876*1024Kbytes
REDBORDERSIZE=627916

# or define this.
# REDLIMIT=0.7

KERNELMEMORY はコメントにあるとおり。32bit モードの Linux は 876Mbyteしかカーネル用空間が無いのでこれが利用できる最大値。

PREVIOUSSTATEは「前回計算した際のステート」。ステートは GREEN, YELLOW, RED のどれかしかないとし、全部文字列で表すことにする。

YELLOWBORDERSIZE は Slab がこの値を超えていたらステートを YELLOW として出力する値。
YELLOWLIMIT は KERNELMEMORY サイズにこの値を乗じて得られた結果を YELLOWBORDERSIZE として用いる、という値。
もし、YELLOWLIMIT と YELLOWBORDERSIZE の両方が定義されていたらどちらか小さい方を採択する。
片方がそんざいしてもう一方が存在しないなら、存在する方が有効になる。どちらも存在しなければ YELLOWBORDERSIZE=358809 が定義されているものとして処理する。

REDBORDERSIZE は Slab がこの値を超えていたらステートを RED として出力する値。
REDLIMIT は KERNELMEMORY サイズにこの値を乗じて得られた結果を REDBORDERSIZE として用いる、という値。
もし、REDLIMIT と REDBORDERSIZE の両方が定義されていたらどちらか小さい方を採択する。
片方がそんざいしてもう一方が存在しないなら、存在する方が有効になる。どちらも存在しなければ REDBORDERSIZE=627916 が定義されているものとして処理する。

もし、YELLOWLIMIT や YELLOWBORDERSIZE が何らかの理由で REDLIMIT や REDBORDERSIZE よりも大きい場合は、自動的に REDLIMIT, REDBORDERSIZE と同じ値にまで下がったものとみなす。

何らかの理由でREDLIMIT, REDBORDERSIZE が KERNELMEMORY 以上の値になった場合は、REDBORDERSIZE=$KERNELMEMORY と見なす。これが意味のある制約かどうかは微妙だが…。

YELLOWLIMIT, YELLOWBORDERSIZE が 0以下の場合はデフォルト値が採択される。

REDLIMIT, REDBORDERSIZE が 0以下の場合はデフォルト値が採択される。



これで判るとおり、実は境界問題はかなり厄介な問題をはらんでいる。与える境界値が適切な場合は特に問題ないのだが、境界値が異常値の場合、妥当に動かすのは著しく困難を伴う。その最大の理由は、プログラマが妥当な状態を決めなくてはいけないからだ。

この問題の本当の難しいところは、条件判断ではない。

妥当ではない計算条件を与えられたときに、それをいかにして跳ね除けるか、という点だ。



さて、プログラムだ。浮動小数点演算を bash で実行するのは難しいので、awk で実装する。いくつかの定数については、awk側に埋め込む。残りの引数は gawk の -v オプションを用いて引き渡す。

CalculateStatus.awk
#! /bin/gawk -f
BEGIN {
KERNELMEMORY = 876 * 1024;
DefaultYELLOWBORDERSIZE = 358809;
DefaultREDBORDERSIZE = 627916;
GreenName = "GREEN";
YellowName = "YELLOW";
RedName = "RED";


# First, calucate PreYELLOWBORDERSIZE without thinking about RED.
if ( YELLOWLIMIT > 0 ) {
PreYELLOWBORDERSIZE = KERNELMEMORY * YELLOWLIMIT;
} else {
PreYELLOWBORDERSIZE = 0;
}
if ( YELLOWBORDERSIZE > 0 ) {
if ( PreYELLOWBORDERSIZE > 0 ) {
if ( PreYELLOWBORDERSIZE > YELLOWBORDERSIZE ) {
PreYELLOWBORDERSIZE = YELLOWBORDERSIZE;
}
} else {
PreYELLOWBORDERSIZE = YELLOWBORDERSIZE;
}
} else {
PreYELLOWBORDERSIZE = DefaultYELLOWBORDERSIZE;
}

# First, calucate PreREDBORDERSIZE without thinking about YELLOW.
if ( REDLIMIT > 0 ) {
PreREDBORDERSIZE = KERNELMEMORY * REDLIMIT;
} else {
PreREDBORDERSIZE = 0;
}
if ( REDBORDERSIZE > 0 ) {
if ( PreREDBORDERSIZE > 0 ) {
if ( PreREDBORDERSIZE > REDBORDERSIZE ) {
PreREDBORDERSIZE = REDBORDERSIZE;
}
} else {
PreREDBORDERSIZE = rEDBORDERSIZE;
}
} else {
PreREDBORDERSIZE = DefaultREDBORDERSIZE;
}

# OK. Re-calculate YELLOW according to RED.
if ( PreREDBORDERSIZE < PreYELLOWBORDERSIZE ) {
PreYELLOWBORDERSIZE = PreREDBORDERSIZE;
}

YELLOWBORDERSIZE = PreYELLOWBORDERSIZE;
REDBORDERSIZE = PreREDBORDERSIZE;

if ( SLAB > REDBORDERSIZE ) {
NewStatus = RedName;
} else if ( Slab > YELLOWBORDERSIZE ) {
NewStatus = YellowName;
} else {
NewStatus = GreenName;
}

# Ok. check old status in mind.
if ( PREVIOUSSTATE == RedName ) {
NewStatus = RedName;
} else if (( PREVIOUSSTATE == YellowName )&&( NewStatus = GreenName )) {
NewStatus = YellowName;
}

print NewStatus;
exit 0;
}

結果は stdout で出て行くので、bash 変数に取り込めばよい。

2009年3月1日

解答を書いておく -2- 部分解

まずは request3 から


request3 は比較的簡単だ。sed でも awk でもできる。

sed ならばこう:
sed 's/^/# /g' /proc/slabinfo


awk ならばこう:
awk '{ print "# " $0; }' /proc/slabinfo


awk の場合は正規表現を必要としない分早いかもしれない。でもそもそも sed の方がプログラムとして小さいのでこちらの方が早いかもしれない。どちらがいいのかはよく判らない。

つぎに request1


/proc/meminfo の各行から数字の部分を切り出すだけなら
awk '{ print $2; }' /proc/meminfo

だけで済む。しかしここには悪魔が潜んでいる。

難しいポイントは3つ。

  1. 時刻を出力する

  2. /proc/meminfo の中身の順序がカーネル依存である

  3. /proc/meminfo の Slab フィールドの値は、request2 でも使う



時刻を出力するには、最初に date 出力を getline で取得すればよい。
BEGIN {
"date +"%Y/%m/%d %H:%M:%S" | getline DATEVAL;
DATETAG="date";
}


/proc/meminfo の中身自体は適当なバージョンにあわせるとしよう。':' よりも左側の文字列を取ってくるには:
sed 's/^\(.*\):.*$/\1/g' /proc/meminfo

とやればよい。結果として
MemTotal
MemFree
Buffers
Cached
SwapCached
:
のような出力が得られる。これをベースに、<各エントリ名>TAG という変数と、<各エントリ名>VALという変数を作ろう。こんな感じだ:
for i in $(sed 's/^\(.*\):.*$/\1/g' /proc/meminfo ); do
echo "/^${i}:/ { ${i}VAL = \$2; ${i}TAG=\"${i}\"; }"
done

結果はこんな感じになる。
/^MemTotal:/ { MemTotalVAL = $2; MemTotalTAG="MemTotal"; }
/^MemFree:/ { MemFreeVAL = $2; MemFreeTAG="MemFree"; }
/^Buffers:/ { BuffersVAL = $2; BuffersTAG="Buffers"; }
/^Cached:/ { CachedVAL = $2; CachedTAG="Cached"; }
/^SwapCached:/ { SwapCachedVAL = $2; SwapCachedTAG="SwapCached"; }
:

各行が awk スクリプトにおける「対応する値をとってくるためのスクリプト」になっている。
同様にして、出力するコードを吐かせる。
for i in $(sed 's/^\(.*\):.*$/\1/g' meminfo ); do
echo " printf(\"\\\"\" ${i}VAL \"\\\"\\t\" );"
done
結果はこんな感じ。
 printf("\"" MemTotalVAL "\"\t" );
printf("\"" MemFreeVAL "\"\t" );
printf("\"" BuffersVAL "\"\t" );
printf("\"" CachedVAL "\"\t" );
printf("\"" SwapCachedVAL "\"\t" );
:
あと、「VAL」の部分を「TAG」に変えたものも用意する。

TAGをいつ出力するのか、その制御は bash 側からすることにしよう。PRINTTAGという変数を用意して、これが 0 だったら TAG を出さない。それ以外だったら出す。

printf の最後の要素だけは \t を \n に直す。また、date を出力するコードを加える。

以上を全部行うとこうなる:
BEGIN {
"date +'%Y/%m/%d %H:%M:%S'" | getline DATEVAL;
DATETAG="date";
}

/^MemTotal:/ { MemTotalVAL = $2; MemTotalTAG="MemTotal"; }
/^MemFree:/ { MemFreeVAL = $2; MemFreeTAG="MemFree"; }
/^Buffers:/ { BuffersVAL = $2; BuffersTAG="Buffers"; }
/^Cached:/ { CachedVAL = $2; CachedTAG="Cached"; }
/^SwapCached:/ { SwapCachedVAL = $2; SwapCachedTAG="SwapCached"; }
/^Active:/ { ActiveVAL = $2; ActiveTAG="Active"; }
/^Inactive:/ { InactiveVAL = $2; InactiveTAG="Inactive"; }
/^HighTotal:/ { HighTotalVAL = $2; HighTotalTAG="HighTotal"; }
/^HighFree:/ { HighFreeVAL = $2; HighFreeTAG="HighFree"; }
/^LowTotal:/ { LowTotalVAL = $2; LowTotalTAG="LowTotal"; }
/^LowFree:/ { LowFreeVAL = $2; LowFreeTAG="LowFree"; }
/^SwapTotal:/ { SwapTotalVAL = $2; SwapTotalTAG="SwapTotal"; }
/^SwapFree:/ { SwapFreeVAL = $2; SwapFreeTAG="SwapFree"; }
/^Dirty:/ { DirtyVAL = $2; DirtyTAG="Dirty"; }
/^Writeback:/ { WritebackVAL = $2; WritebackTAG="Writeback"; }
/^Mapped:/ { MappedVAL = $2; MappedTAG="Mapped"; }
/^Slab:/ { SlabVAL = $2; SlabTAG="Slab"; }
/^CommitLimit:/ { CommitLimitVAL = $2; CommitLimitTAG="CommitLimit"; }
/^Committed_AS:/ { Committed_ASVAL = $2; Committed_ASTAG="Committed_AS"; }
/^PageTables:/ { PageTablesVAL = $2; PageTablesTAG="PageTables"; }
/^VmallocTotal:/ { VmallocTotalVAL = $2; VmallocTotalTAG="VmallocTotal"; }
/^VmallocUsed:/ { VmallocUsedVAL = $2; VmallocUsedTAG="VmallocUsed"; }
/^VmallocChunk:/ { VmallocChunkVAL = $2; VmallocChunkTAG="VmallocChunk"; }
/^HugePages_Total:/ { HugePages_TotalVAL = $2; HugePages_TotalTAG="HugePages_Total"; }
/^HugePages_Free:/ { HugePages_FreeVAL = $2; HugePages_FreeTAG="HugePages_Free"; }
/^Hugepagesize:/ { HugepagesizeVAL = $2; HugepagesizeTAG="Hugepagesize"; }

END {
if ( PRINTTAG != 0 ) {
printf("\"" DATETAG "\"\t" );
printf("\"" MemTotalTAG "\"\t" );
printf("\"" MemFreeTAG "\"\t" );
printf("\"" BuffersTAG "\"\t" );
printf("\"" CachedTAG "\"\t" );
printf("\"" SwapCachedTAG "\"\t" );
printf("\"" ActiveTAG "\"\t" );
printf("\"" InactiveTAG "\"\t" );
printf("\"" HighTotalTAG "\"\t" );
printf("\"" HighFreeTAG "\"\t" );
printf("\"" LowTotalTAG "\"\t" );
printf("\"" LowFreeTAG "\"\t" );
printf("\"" SwapTotalTAG "\"\t" );
printf("\"" SwapFreeTAG "\"\t" );
printf("\"" DirtyTAG "\"\t" );
printf("\"" WritebackTAG "\"\t" );
printf("\"" MappedTAG "\"\t" );
printf("\"" SlabTAG "\"\t" );
printf("\"" CommitLimitTAG "\"\t" );
printf("\"" Committed_ASTAG "\"\t" );
printf("\"" PageTablesTAG "\"\t" );
printf("\"" VmallocTotalTAG "\"\t" );
printf("\"" VmallocUsedTAG "\"\t" );
printf("\"" VmallocChunkTAG "\"\t" );
printf("\"" HugePages_TotalTAG "\"\t" );
printf("\"" HugePages_FreeTAG "\"\t" );
printf("\"" HugepagesizeTAG "\"\n" );
}

printf("\"" DATEVAL "\"\t" );
printf("\"" MemTotalVAL "\"\t" );
printf("\"" MemFreeVAL "\"\t" );
printf("\"" BuffersVAL "\"\t" );
printf("\"" CachedVAL "\"\t" );
printf("\"" SwapCachedVAL "\"\t" );
printf("\"" ActiveVAL "\"\t" );
printf("\"" InactiveVAL "\"\t" );
printf("\"" HighTotalVAL "\"\t" );
printf("\"" HighFreeVAL "\"\t" );
printf("\"" LowTotalVAL "\"\t" );
printf("\"" LowFreeVAL "\"\t" );
printf("\"" SwapTotalVAL "\"\t" );
printf("\"" SwapFreeVAL "\"\t" );
printf("\"" DirtyVAL "\"\t" );
printf("\"" WritebackVAL "\"\t" );
printf("\"" MappedVAL "\"\t" );
printf("\"" SlabVAL "\"\t" );
printf("\"" CommitLimitVAL "\"\t" );
printf("\"" Committed_ASVAL "\"\t" );
printf("\"" PageTablesVAL "\"\t" );
printf("\"" VmallocTotalVAL "\"\t" );
printf("\"" VmallocUsedVAL "\"\t" );
printf("\"" VmallocChunkVAL "\"\t" );
printf("\"" HugePages_TotalVAL "\"\t" );
printf("\"" HugePages_FreeVAL "\"\t" );
printf("\"" HugepagesizeVAL "\"\n" );
}

このスクリプト全体を、meminfo1.awk として保存する。で、/proc/meminfo を食わせてみるとこんな感じになる。
$ gawk -v PRINTTAG=1 -f meminfo1.awk /proc/meminfo 
"date" "MemTotal" "MemFree" "Buffers" "Cached" "SwapCached" "Active" "Inactive" "HighTotal" "HighFree" "LowTotal" "LowFree" "SwapTotal" "SwapFree" "Dirty" "Writeback" "Mapped" "Slab" "CommitLimit" "Committed_AS" "PageTables" "VmallocTotal" "VmallocUsed" "VmallocChunk" "HugePages_Total" "HugePages_Free" "Hugepagesize"
"2009/03/01 23:00:00" "1001008" "200708" "43400" "395740" "0" "557556" "178624" "97216" "140" "903792" "200568" "2096472" "2096472" "224" "0" "356492" "47820" "2596976" "689048" "6192" "114680" "4560" "107264" "0" "0" "2048"
$ gawk -v PRINTTAG=0 -f meminfo1.awk /proc/meminfo
"2009/03/01 23:00:37" "1001008" "200708" "43400" "395740" "0" "557556" "178624" "97216" "140" "903792" "200568" "2096472" "2096472" "224" "0" "356492" "47820" "2596976" "689048" "6192" "114680" "4560" "107264" "0" "0" "2048"
$ gawk -f meminfo1.awk /proc/meminfo
"2009/03/01 23:00:37" "1001008" "200708" "43400" "395740" "0" "557556" "178624" "97216" "140" "903792" "200568" "2096472" "2096472" "224" "0" "356492" "47820" "2596976" "689048" "6192" "114680" "4560" "107264" "0" "0" "2048"

-v を使って PRINTTAG 変数をスクリプト外部から制御した。最後の -v を指定しない例は、ようするに「デフォルトの挙動」と言う奴になる。

やれこれで一安心…ではない。Slab の値だけ、別途別パスで外に出してやる必要がある。幸い、stderr という文明の利器があるのでこれを使う。
    print SlabVAL > "/dev/stderr";

これを END {} セクションの最後に加えるのだ。そうすると Slabの数字の部分だけが stderr で取り出せる。

request1に対する最終 meminfo.awk スクリプトはこうなる:
BEGIN {
"date +'%Y/%m/%d %H:%M:%S'" | getline DATEVAL;
DATETAG="date";
}

/^MemTotal:/ { MemTotalVAL = $2; MemTotalTAG="MemTotal"; }
/^MemFree:/ { MemFreeVAL = $2; MemFreeTAG="MemFree"; }
/^Buffers:/ { BuffersVAL = $2; BuffersTAG="Buffers"; }
/^Cached:/ { CachedVAL = $2; CachedTAG="Cached"; }
/^SwapCached:/ { SwapCachedVAL = $2; SwapCachedTAG="SwapCached"; }
/^Active:/ { ActiveVAL = $2; ActiveTAG="Active"; }
/^Inactive:/ { InactiveVAL = $2; InactiveTAG="Inactive"; }
/^HighTotal:/ { HighTotalVAL = $2; HighTotalTAG="HighTotal"; }
/^HighFree:/ { HighFreeVAL = $2; HighFreeTAG="HighFree"; }
/^LowTotal:/ { LowTotalVAL = $2; LowTotalTAG="LowTotal"; }
/^LowFree:/ { LowFreeVAL = $2; LowFreeTAG="LowFree"; }
/^SwapTotal:/ { SwapTotalVAL = $2; SwapTotalTAG="SwapTotal"; }
/^SwapFree:/ { SwapFreeVAL = $2; SwapFreeTAG="SwapFree"; }
/^Dirty:/ { DirtyVAL = $2; DirtyTAG="Dirty"; }
/^Writeback:/ { WritebackVAL = $2; WritebackTAG="Writeback"; }
/^Mapped:/ { MappedVAL = $2; MappedTAG="Mapped"; }
/^Slab:/ { SlabVAL = $2; SlabTAG="Slab"; }
/^CommitLimit:/ { CommitLimitVAL = $2; CommitLimitTAG="CommitLimit"; }
/^Committed_AS:/ { Committed_ASVAL = $2; Committed_ASTAG="Committed_AS"; }
/^PageTables:/ { PageTablesVAL = $2; PageTablesTAG="PageTables"; }
/^VmallocTotal:/ { VmallocTotalVAL = $2; VmallocTotalTAG="VmallocTotal"; }
/^VmallocUsed:/ { VmallocUsedVAL = $2; VmallocUsedTAG="VmallocUsed"; }
/^VmallocChunk:/ { VmallocChunkVAL = $2; VmallocChunkTAG="VmallocChunk"; }
/^HugePages_Total:/ { HugePages_TotalVAL = $2; HugePages_TotalTAG="HugePages_Total"; }
/^HugePages_Free:/ { HugePages_FreeVAL = $2; HugePages_FreeTAG="HugePages_Free"; }
/^Hugepagesize:/ { HugepagesizeVAL = $2; HugepagesizeTAG="Hugepagesize"; }

END {
if ( PRINTTAG != 0 ) {
printf("\"" DATETAG "\"\t" );
printf("\"" MemTotalTAG "\"\t" );
printf("\"" MemFreeTAG "\"\t" );
printf("\"" BuffersTAG "\"\t" );
printf("\"" CachedTAG "\"\t" );
printf("\"" SwapCachedTAG "\"\t" );
printf("\"" ActiveTAG "\"\t" );
printf("\"" InactiveTAG "\"\t" );
printf("\"" HighTotalTAG "\"\t" );
printf("\"" HighFreeTAG "\"\t" );
printf("\"" LowTotalTAG "\"\t" );
printf("\"" LowFreeTAG "\"\t" );
printf("\"" SwapTotalTAG "\"\t" );
printf("\"" SwapFreeTAG "\"\t" );
printf("\"" DirtyTAG "\"\t" );
printf("\"" WritebackTAG "\"\t" );
printf("\"" MappedTAG "\"\t" );
printf("\"" SlabTAG "\"\t" );
printf("\"" CommitLimitTAG "\"\t" );
printf("\"" Committed_ASTAG "\"\t" );
printf("\"" PageTablesTAG "\"\t" );
printf("\"" VmallocTotalTAG "\"\t" );
printf("\"" VmallocUsedTAG "\"\t" );
printf("\"" VmallocChunkTAG "\"\t" );
printf("\"" HugePages_TotalTAG "\"\t" );
printf("\"" HugePages_FreeTAG "\"\t" );
printf("\"" HugepagesizeTAG "\"\n" );
}

printf("\"" DATEVAL "\"\t" );
printf("\"" MemTotalVAL "\"\t" );
printf("\"" MemFreeVAL "\"\t" );
printf("\"" BuffersVAL "\"\t" );
printf("\"" CachedVAL "\"\t" );
printf("\"" SwapCachedVAL "\"\t" );
printf("\"" ActiveVAL "\"\t" );
printf("\"" InactiveVAL "\"\t" );
printf("\"" HighTotalVAL "\"\t" );
printf("\"" HighFreeVAL "\"\t" );
printf("\"" LowTotalVAL "\"\t" );
printf("\"" LowFreeVAL "\"\t" );
printf("\"" SwapTotalVAL "\"\t" );
printf("\"" SwapFreeVAL "\"\t" );
printf("\"" DirtyVAL "\"\t" );
printf("\"" WritebackVAL "\"\t" );
printf("\"" MappedVAL "\"\t" );
printf("\"" SlabVAL "\"\t" );
printf("\"" CommitLimitVAL "\"\t" );
printf("\"" Committed_ASVAL "\"\t" );
printf("\"" PageTablesVAL "\"\t" );
printf("\"" VmallocTotalVAL "\"\t" );
printf("\"" VmallocUsedVAL "\"\t" );
printf("\"" VmallocChunkVAL "\"\t" );
printf("\"" HugePages_TotalVAL "\"\t" );
printf("\"" HugePages_FreeVAL "\"\t" );
printf("\"" HugepagesizeVAL "\"\n" );

print SlabVAL > "/dev/stderr";
}

STDOUT はログファイルに出力させる。その後 STDERR を stdout にリダイレクトして、変数に食わせてやる。実行する側のシェルはこんな感じ:
SLAB=$(( awk -f meminfo.awk /proc/meminfo >> $LOGFILE ) 2>&1 )

これで SLAB 変数には Slab: の数字のフィールドが入り、かつ全部の出力が $LOGFILE に出力される。

解答を書いておく -1- 課題

仕事で、ある人にスクリプト作成を依頼している。
内容的には単純なのだが、ちょっとややこしい部分があって、論理的に考えないと答が出ないようになっている。半分、演習問題のようなテーマだ。

順調に遅延しているのはいいのだが、私自身他に3件も受け持っているので、
「いざ、もう間に合いません」
と言うときになってからスクリプトを書き始めると、ちょっと間に合わなかったりする。なので、ここに模範解答の形で答を書いておこうか、と思う。いざとなったらここを参照すればよいわけだ。

他の人の演習問題にもなったらな、と思うので、問題も全部公開しよう。秘密といえるような部分は一切無い。



課題


request1


/proc/meminfo の内容を定期的に参照して欲しい。/proc/meminfo のデータは1項目一行形式になっているので、これを1行の TSV に直して欲しい。最初の一回だけ、どの項目がどこにあるのかを示すラベルもつけて欲しい。また、最初の要素に、/proc/meminfo の情報をいつ取得したのか、date の情報を "YYYY/MM/DD hh:mm:ss" の形式で保存して欲しい。

request2


/proc/meminfo には Slab という項目がある。ここの値がある閾値を超えたら、定期的に参照するその参照頻度を上げて欲しい。ようするにデータを取る間隔を短くして欲しいのだ。問題は、いったん間隔を短くしたら、二度と長くしないで欲しい、と言うことと、「閾値」は2つ、Yellow と Red があるので、それぞれに応じて間隔を短くして欲しい。また、この閾値はあとで簡単に変更できるように、スクリプトの上のほうの行で名前で宣言して欲しい。もちろん、その際の「間隔」もそれぞれ名前で宣言しておいて欲しい。

request3


Yellow の閾値を超えたら /proc/slabinfo のデータも取ってきて欲しい。こちらは、/proc/slabinfo のファイルの情報の各行の前に "# " (シャープがきて、その次に空白が1つ) をつけた形で、/proc/meminfo のデータを保存しているログファイルと同じ所に保存して欲しい。

課題の詳細


request1


/proc/meminfo を読んでくると次のようなフォーマットで値が出力される。
MemTotal:      1001008 kB
MemFree: 200708 kB
Buffers: 43400 kB
Cached: 395740 kB
SwapCached: 0 kB
Active: 557556 kB
Inactive: 178624 kB
HighTotal: 97216 kB
HighFree: 140 kB
LowTotal: 903792 kB
LowFree: 200568 kB
SwapTotal: 2096472 kB
SwapFree: 2096472 kB
Dirty: 224 kB
Writeback: 0 kB
Mapped: 356492 kB
Slab: 47820 kB
CommitLimit: 2596976 kB
Committed_AS: 689048 kB
PageTables: 6192 kB
VmallocTotal: 114680 kB
VmallocUsed: 4560 kB
VmallocChunk: 107264 kB
HugePages_Total: 0
HugePages_Free: 0
Hugepagesize: 2048 kB

request 1 はようするにこれを

"1001008"<tab>"200708"<tab>"43400"....

のように1行に直して欲しい、という事だ。ただし、<tab> の部分は実際にはタブ文字コード (awk とかだと "\t")で分けて欲しい。また、数字は " で囲ってもらえると助かる。

また、最初の1回だけラベルの部分も欲しい。これはつまり
"MemTotal"<tab>"MemFree"<tab>"Buffers"....

のような行を出して欲しい、と言うこと。

さらに、一番最初にデータを取得した情報が欲しい、とある。これはようするに
date +"%Y/%m/%d %H:%M:%S"

の出力を取り込んで、最初に表示して欲しい、という事だ。当然ラベル行も実際には:
"date"<tab>"MemTotal"<tab>"MemFree"<tab>"Buffers"....

でなくては困る。

request2


/proc/meminfo のSlab:の項目が 358809 未満を Green,
358809 以上 627916 未満を Yellow,
627916 以上を Red
という状態とする。これは32bit Linux Kernel のメモリ領域が 876Mbyte しかなく、それぞれ 40%、70% の所に閾値を用意した、という事だ。実際には Slab が Yellow になることすらめったに無いはずで、Red に突入したら、間違いなく oom-killer が近々発動するだろう。その傾向を見たいのだ。

状態が Green の間は 300秒(5分)間隔で request1 を実施して欲しい。
Yellow になったら 60秒(1分)間隔で request1 と request3 を実施して欲しい。
Red になったら 30秒間隔で request1 と request3 を実施して欲しい。

いったん Yellow になったら、Slab の値が改善しても Green には戻らないで欲しい。同様に Red になったら、Yellow, Green には戻ってはいけない。なので、「今回の Slab の値だけで、次にデータ取得するまでの間隔を決める」のでは困る。

request3


cat /proc/slabinfo を実施するとこんな感じの出力が得られる。
slabinfo - version: 1.1
kmem_cache 60 78 100 2 2 1
blkdev_requests 5120 5120 96 128 128 1
mnt_cache 20 40 96 1 1 1
inode_cache 7005 14792 480 1598 1849 1
dentry_cache 5469 5880 128 183 196 1
filp 726 760 96 19 19 1
buffer_head 67131 71240 96 1776 1781 1
vm_area_struct 1204 1652 64 23 28 1

これの頭に "# " をつけて
# slabinfo - version: 1.1
# kmem_cache 60 78 100 2 2 1
# blkdev_requests 5120 5120 96 128 128 1
# mnt_cache 20 40 96 1 1 1
# inode_cache 7005 14792 480 1598 1849 1
# dentry_cache 5469 5880 128 183 196 1
# filp 726 760 96 19 19 1
# buffer_head 67131 71240 96 1776 1781 1
# vm_area_struct 1204 1652 64 23 28 1

としてほしいわけだ。これは、/proc/meminfo の次の行から出力して欲しい。だから:
"2009/03/01 10:48:58"<tab>"1001008"<tab>"200708"<tab>"43400"....
# slabinfo - version: 1.1
# kmem_cache 60 78 100 2 2 1
# blkdev_requests 5120 5120 96 128 128 1
# mnt_cache 20 40 96 1 1 1
# inode_cache 7005 14792 480 1598 1849 1
# dentry_cache 5469 5880 128 183 196 1
# filp 726 760 96 19 19 1
# buffer_head 67131 71240 96 1776 1781 1
# vm_area_struct 1204 1652 64 23 28 1

:

のような出力が延々続くログファイルが欲しい、という事になる。

背景


oom-killer が発動したシステムがあった。
/var/log/messages に残る oom-killer の行動痕跡によると、slab が異常に大きい。
そこで、slab の状態を記録すると同時に、自分たちが何をやっていたのかを記録する。

slab がでかくなったタイミングで何をやっていたのか知れば、問題点がわかるに違いない。そのための監視用ログシステムが欲しい、と言うわけ。

参考資料


スクリプト言語であれば何を使っても良かったのだが、とりあえず bash と sed と awk ということなので、参考資料は次の通り。

2009年2月13日

似たようなことがしてみたい人へ

えー、一応。似たようなことをしたい人がいる可能性を考えて、参考資料を。まずは上司/スポンサー説得資料
基本的にこれらの本に書いてあるのとやっていることは同じである、と主張しても大きく嘘にはならないと思います。いや、本当に同じ様なことをしているんですけれどね。統計学を仕事に応用しているんだし。ただ、あまりすさまじくかっこいい具合にありえないようなものを発見できる、と言う風に誤解されるとそれはそれで後が大変でしょうから、こう…加減と言うものは各自で調整してください。

で、これで何を手に入れるかと言うと、もちろん「計算機」です。大事なのは「64bit」と「メモリがたくさん」。いまどきのx86マシンならば EM64T対応な段階で SSEとかも相応に早いです。もちろん、Core2Duo とか Core2Quad とか Corei7 とかがいいわけですが、動作クロックは最低でも構わないかと。むしろ大事なのはメモリ。

Windows はやめておいた方がよいです。メモリの利用効率が悪い、あれは。ファイルキャッシュのデザインが unix 以前(1969年以前、と言うこと)のOSそっくりだ。計算用データを保持するために1pageでも余計にメモリを使いたいときに、ガッツリ kernel 内部のファイルキャッシュがページを握って離さない、というWindows NT/Longhorn デザインは勘弁して欲しい(つーか Windows7 では是非その辺りも改善していただきたいものだ)。と言うわけで、とりあえずお勧めは Linux 。マルチコア、HT対応、Intel & AMD 御謹製のプロセッサコントロールコードが使えるのは Linux ぐらいですから。

次に。あなた自身のための入門本。
多分、この一冊と Wikipedia 辺りが入門には丁度良いのではないかと。もちろん、前出の2冊も読んでいただくのですがあれは「どういう場面で統計を使うか」なのに対して、ここからは「その場面で何を使うか」。

で、最後に「なんとなく判ったけど、コーディングが面倒だな」とか「もっと詳しく知りたいが、できれば数学的なほうじゃなくて…」という人のための本。
多分これぐらいあれば、1年はここに書いてある内容で遊べます。

えぇ、「遊んで暮らせます」というオチだったらよかったんですけどねぇ。残念ながらそこまでは…

2009年2月11日

リファクタリング-5-

さて。一番重たい部分を計算するプログラム公開の前に。どうやら問題を理解してもらえていないようなので、もう一度書いておく。

m 個のデータ列がある。各データ列は N 個のデータからなる。N個のデータはm個のデータ列の間で互いにどれとどれが対応するのかは判っているとする。

これらのデータ列同士の相関を「総当りで」求めたい。相関係数 r は r(x,y) と r(y,x) (ただし、x, y はデータ列) は同じ値になるので、m個のデータ列同士の総当たり戦は m*(m-1)/2 通りになる。これを
面倒なコーディングを可能な限り行わない
形で、実用的な速度で解きたい。

R であれ、ライブラリであれ、モジュールであれ、1組2個のデータ列同士の相関を求める関数は存在するし、それはどれを使っても相応に早い。しかし「総当たり戦」の場合、その関数を m*(m-1)/2 回呼び出さなくてはいけない。

相関を求める式には、データ列単位で計算しておけばそれを使いまわせるものが結構ある。平均値を求めたり、各データと当該平均値との差分を求めたり、それら差分の二乗値の和の平方根を取ったりした結果などがそうだ。
ある任意のデータ列は、(m-1)/2 回相関を求めるために呼び出される。と言うことは、上記の値は、最初に1回だけ計算しておけば ((m-1)/2) - 1 回分、計算をしなくてよい。

このような最適化を施したライブラリは、いまだに存在しない。というか、存在しなくは無いのだが、メモリを食いすぎるため、私のPCでは遅すぎるのだ(32bit環境だと、そもそもアドレス空間が足りなくて core 吐いて終わる。64bit 環境は、メモリが足りないため swap IO ばかりで計算が全然前に進まない)。計算環境が64bit環境だけで作れて、メモリが潤沢にあれば問題は無いのだが…

その一方で。この相関は何のためにとるのかと言うと、「Windows の perfmon の出力から、何か性能劣化の兆候が見えないか?」とか「性能が劣化しているのだが、何が原因かわからないか?」という分析をする際の、調査対象に優先度をつけるために行っている。つまり計算誤差なんぞ多少あっても構わないのだ。まじめな数値計算ではなく、誤差が多少あってもいいから高速に結果を出して欲しい。

実用になる範囲であれば、perl 辺りでライブラリを叩けば十分だったのだが、速度が遅すぎる以上 C でコーディングをするしかないわけだ。

ちなみに、mは 27000.Nが 675。 perl で組んでいたときは、1組当たり 9msec かかっていた。つまり37.9673.. 38日かかる計算になる。これが C だと 37分…まぁIOの関係で40分ぐらい。「値が変化しない」とわかりきっている部分が半分近くあり、それらについては相関は絶対 NaN になると判っているのでメモリも消費しなくなった。すると32bit環境でも一発で計算できるではないか…。NIH(Not Invented Here)症候群と言われようが、自分でコーディングする気になるというものだよ。


次は一番重たい部分を計算するCのプログラム

correlation.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <math.h>

#define DEBUG (0)



typedef struct __data {
struct __data *next;
struct __data *prev;
char *element_name;
int correlation;
double sum;
double mean;
double sqsum;
double subpart;
long numdata;
double *datalist;
} data;


data* read_each( char *listfilename );
data* read_datafile( char *linebuffer );
void add_data_array( data **datalistpp, data *new_datap );
void calculate( data *data_list );
void remove_trailing_newline( char *linebuffer );

void help( char *programname );
void *o_malloc( size_t size, const char *msg, const char *func, const long line );



int
main( int argc, char *argv[] )
{
data *data_list;
if ( argc != 2 ) {
help( argv[0] );
}

data_list = read_each( argv[1] );
if ( data_list == NULL ) {
fprintf( stderr, "no data given.\n" );
exit( 1 );
}

calculate( data_list );
}

data*
read_each( char *listfilename )
{
FILE *fp;
char *linebuffer;
int linebufsize;
data *read_datap = NULL;


linebufsize = getpagesize();
linebuffer = o_malloc( linebufsize, "unable to prepare linebuffer\n", __func__, __LINE__ );

fp = fopen( listfilename, "r" );
if ( fp == NULL ) {
fprintf( stderr, "unable to open file %s\n", listfilename );
exit( 1 );
}

while( fgets( linebuffer, linebufsize, fp ) != NULL ) {
data *new_datap;
long len;

if ( linebuffer[0] == '#' ) {
continue;
}

remove_trailing_newline( linebuffer );

new_datap = read_datafile( linebuffer );
if ( new_datap->correlation != 0 ) {
add_data_array( &read_datap, new_datap );
} else {
/* take it away from the memory.*/
free( new_datap->datalist );
free( new_datap->element_name );
free( new_datap );
}
}
fclose( fp );

return read_datap;
}

data*
read_datafile( char *datafilename )
{
data *newdata;
FILE *fp;
char *linebuffer;
size_t linebufsize;
char *tagname;


newdata = o_malloc( sizeof( data ), "unable to allocate memory for newdata\n", __func__, __LINE__ );

linebufsize = getpagesize();
linebuffer = o_malloc( linebufsize, "unable to prepare linebuffer for read_datafile()\n", __func__, __LINE__ );

fp = fopen( datafilename, "r" );
if ( fp == NULL ) {
fprintf( stderr, "unable to open file %s as data file target\n", datafilename );
exit( 1 );
}


/* read tagname */
{
char *p;
long dataoffset;

if( fgets( linebuffer, linebufsize, fp ) == NULL ) {
fprintf( stderr, "unable to read tagname at file %s\n", datafilename );
exit( 1 );
}
remove_trailing_newline( linebuffer );

p = strstr( linebuffer, "tagname\t" );
if ( p != linebuffer ) {
fprintf( stderr, "unable to find \"tagname\t\" indicator in %s\n", datafilename );
exit( 1 );
}

dataoffset = strlen( "tagname\t" );

newdata->element_name = strdup( linebuffer + dataoffset );
if ( newdata->element_name == NULL ) {
fprintf( stderr, "unable to copy tagname %s in %s\n",
linebuffer + dataoffset, datafilename );
exit( 1 );
}
#if DEBUG
fprintf( stderr, "tagname was %s\n", newdata->element_name );
#endif/*DEBUG*/
}

/* read Correlation */
{
char *p;
long dataoffset;

if( fgets( linebuffer, linebufsize, fp ) == NULL ) {
fprintf( stderr, "unable to read Correlation at file %s\n", datafilename );
exit( 1 );
}
p = strstr( linebuffer, "Correlation\t" );
if ( p != linebuffer ) {
fprintf( stderr, "unable to find \"Correlation\t\" indicator in %s\n", datafilename );
exit( 1 );
}
dataoffset = strlen( "Correlation\t" );
if ( strncasecmp( linebuffer + dataoffset, "OK", 2 ) != 0 ) {
newdata->correlation = 0;
} else {
newdata->correlation = 1;
}
#if DEBUG
fprintf( stderr, "correlation was %d\n", newdata->correlation );
#endif/*DEBUG*/
}


/* read sum */
{
char *p;
long dataoffset;
int res;

if( fgets( linebuffer, linebufsize, fp ) == NULL ) {
fprintf( stderr, "unable to read sum at file %s\n", datafilename );
exit( 1 );
}
p = strstr( linebuffer, "sum\t" );
if ( p != linebuffer ) {
fprintf( stderr, "unable to find \"sum\t\" indicator in %s\n", datafilename );
exit( 1 );
}
dataoffset = strlen( "sum\t" );
newdata->sum = 0.0;
res = sscanf( linebuffer + dataoffset, "%lf", &( newdata->sum ));
if ( res != 1 ) {
perror( "sscanf failed when reading sum.\n" );
exit( 1 );
}
#if DEBUG
fprintf( stderr, "sum was %lf\n", newdata->sum );
#endif/*DEBUG*/
}


/* read mean */
{
char *p;
long dataoffset;
int res;

if( fgets( linebuffer, linebufsize, fp ) == NULL ) {
fprintf( stderr, "unable to read mean at file %s\n", datafilename );
exit( 1 );
}
p = strstr( linebuffer, "mean\t" );
if ( p != linebuffer ) {
fprintf( stderr, "unable to find \"mean\t\" indicator in %s\n", datafilename );
exit( 1 );
}
dataoffset = strlen( "mean\t" );
newdata->mean = 0.0;
res = sscanf( linebuffer + dataoffset, "%lf", &( newdata->mean ));
if ( res != 1 ) {
perror( "sscanf failed when reading mean.\n" );
exit( 1 );
}
#if DEBUG
fprintf( stderr, "mean was %lf\n", newdata->mean );
#endif/*DEBUG*/
}


/* read sqsum */
{
char *p;
long dataoffset;
int res;

if( fgets( linebuffer, linebufsize, fp ) == NULL ) {
fprintf( stderr, "unable to read sqsum at file %s\n", datafilename );
exit( 1 );
}
p = strstr( linebuffer, "sqsum\t" );
if ( p != linebuffer ) {
fprintf( stderr, "unable to find \"sqsum\t\" indicator in %s\n", datafilename );
exit( 1 );
}
dataoffset = strlen( "sqsum\t" );
newdata->sqsum = 0.0;
res = sscanf( linebuffer + dataoffset, "%lf", &( newdata->sqsum ));
if ( res != 1 ) {
perror( "sscanf failed when reading sqsum.\n" );
exit( 1 );
}
#if DEBUG
fprintf( stderr, "sqsum was %lf\n", newdata->sqsum );
#endif/*DEBUG*/
}


/* read subpart */
{
char *p;
long dataoffset;
int res;

if( fgets( linebuffer, linebufsize, fp ) == NULL ) {
fprintf( stderr, "unable to read subpart at file %s\n", datafilename );
exit( 1 );
}
p = strstr( linebuffer, "subpart\t" );
if ( p != linebuffer ) {
fprintf( stderr, "unable to find \"subpart\t\" indicator in %s\n", datafilename );
exit( 1 );
}
dataoffset = strlen( "subpart\t" );
newdata->subpart = 0.0;
res = sscanf( linebuffer + dataoffset, "%lf", &( newdata->subpart ));
if ( res != 1 ) {
perror( "sscanf failed when reading subpart.\n" );
exit( 1 );
}
#if DEBUG
fprintf( stderr, "subpart was %lf\n", newdata->subpart );
#endif/*DEBUG*/
}


/* read datas */
{
char *p;
long dataoffset;
int res;

if( fgets( linebuffer, linebufsize, fp ) == NULL ) {
fprintf( stderr, "unable to read datas at file %s\n", datafilename );
exit( 1 );
}
p = strstr( linebuffer, "datas\t" );
if ( p != linebuffer ) {
fprintf( stderr, "unable to find \"datas\t\" indicator in %s\n", datafilename );
exit( 1 );
}
dataoffset = strlen( "datas\t" );
newdata->numdata = 0;
res = sscanf( linebuffer + dataoffset, "%ld", &( newdata->numdata ));
if ( res != 1 ) {
perror( "sscanf failed when reading datas.\n" );
exit( 1 );
}
#if DEBUG
fprintf( stderr, "datas was %d\n", newdata->numdata );
#endif/*DEBUG*/
}

/* read data list */
{
long i;
int res;

newdata->datalist
= malloc( sizeof( double ) * newdata->numdata );
if ( newdata->datalist == NULL ) {
fprintf( stderr, "not enough memory for datalist: %s\n", datafilename );
exit( 1 );
}

if( fgets( linebuffer, linebufsize, fp ) == NULL ) {
fprintf( stderr, "unable to read empty line at file %s\n", datafilename );
exit( 1 );
}

for ( i = 0; i < newdata->numdata; i++ ) {
if( fgets( linebuffer, linebufsize, fp ) == NULL ) {
fprintf( stderr, "unable to read datas at file %s\n", datafilename );
exit( 1 );
}
res = sscanf( linebuffer, "%lf", &( newdata->datalist[i] ));
/* if we failed in reading data, that means that element should be 0.0 */
if ( res != 1 ) {
newdata->datalist[i] = 0.0;
}

#if DEBUG
fprintf( stderr, "datas was %lf\n", newdata->datalist[i] );
#endif/*DEBUG*/
}
}

fclose( fp );
return newdata;
}

void
add_data_array( data **datalistpp, data *new_datap )
{
if (( *datalistpp ) == NULL ) {
new_datap->next = NULL;
new_datap->prev = NULL;
(*datalistpp) = new_datap;
} else {
new_datap->next = (*datalistpp);
new_datap->prev = NULL;
(*datalistpp)->prev = new_datap;
(*datalistpp) = new_datap;
}
}


void calculate( data *data_list )
{
data *one, *two;
double cor, cor2;
int i;

/* First, change all the data in datalist[] to be subtraction of each's mean */
for ( one = data_list; one != NULL; one = one->next ) {
for ( i = 0; i < one->numdata; i++ ) {
one->datalist[i] -= one->mean;
}
}


for ( one = data_list; one != NULL; one = one->next ) {
for ( two = one->next; two != NULL; two = two->next ) {
cor = 0.0;

for ( i = 0; i < one->numdata; i++ ) {
cor += one->datalist[i] * two->datalist[i];
}
cor /= ( one->subpart * two->subpart );
cor2 = cor * cor;

fprintf( stdout,
"%s\t%s\t%lf\t%lf\n",
one->element_name,
two->element_name,
cor, cor2 );
}
}
}

void remove_trailing_newline( char *linebuffer )
{
size_t len;

while( 1 ) {
len = strlen( linebuffer );
if ( linebuffer[len-1] == '\n' ) {
linebuffer[len-1] = '\0';
} else {
break;
}
}
}


void
help( char *programname )
{
fprintf( stderr,
"%s <filename containing target>\n",
programname );
exit( 127 );
}

void*
o_malloc( size_t size, const char *msg, const char *func, const long line )
{
void *p;
p = malloc( size );
if ( p == NULL ) {
fprintf( stderr, "%s:%ld: %s", func, line, msg );
exit( 1 );
}
}

みての通り、ファイルを読んでくるところの処理が延々。実際の計算部分は calculate() の部分だけ。しかもこれすら標準出力への出力コードが含まれているのに、このサイズ。あぁ、Cの最大の弱点はファイルIOの記述/メモリ管理の記述が面倒くさいことだなぁ。

ちなみに。「C++の例外処理で書けばいいじゃない」という提案をいただいた。が、その手段は使えないし、趣味として使わない。例外処理は素晴らしい機能だ。ただし、あれは「コード的には正しいが、動作環境との兼ね合いで不幸に至った場合」に、どこで不幸を食らったかに依存せず「不幸を食らった事実だけ」を書けばよいから素晴らしいのだ。

しかし、上記のコード、多分端々に見えているだろうが、「おかしくなるのは私のコーディングが間違っているから」というケースに対応するためのものだ。実際に発生した現象としては:
  1. tagname を引っ張ってきたのはいいが、行末の "\n" を削り忘れていた。
  2. Correlation のOK を case insensitive にチェックしているが、実は最初のコードでは "OK" に対して入力は "OK\n" で合致しない攻撃をくらいまくっていた。
  3. Correlation が NG の場合、計算対象からはずすためにリンクリストに登録しないのだが、そのメモリを「放置」していた(メモリリークだ)。64bit環境では特に問題は無かった(ちょっと swap IO するだけ)のだが、32bit環境に持ってきたらパンクした。
  4. Correlation 自身はOKであっても、データが全フィールドに渡って存在するとは限らない。存在しないエントリは 0.0 として扱うことになっていたのだが、sscanf() の戻り値をチェックするときにそのことを完全に失念していて、おかしな部分で give up して exit() していた。
まぁ、そういうケースに対応するための「デバッガコード」であって、異常系ではなかったりする。例外処理だと、「どこでおかしいのか」も判らないし、特に 1 のケースなどは assert() にもひっかけようがない。

先にも書いたが、1データ組み合わせ当たり 6.74usec で計算できた。はーやーいー。9msec とは比べ物にならない。やっぱりコンパイラ言語は本気を出すとすごいわ。

2009年2月5日

リファクタリング-4-

リファクタリング-3-で書いた「データ列を1データ列1ファイルに分割する」プログラムをまず、作った。

remove_NaN.pl:
#! /usr/bin/perl
#
# remove_NaN.pl <dataFileName> <template of eachoutput>
# chop dataFile into each row. If each row is clear that
# "Pearson product-moment correlation coefficient" calculation
# will ends in NaN, do not output that entry.
#
# For <template of eachoutput> I mean something like "hogehoge%08d".
# output file will have row number as for %08d part (%08d can be %d
# or anything in integer format ).

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

our $at_once = 7000;

$datafilename = $ARGV[0];
$outtemplate = $ARGV[1];

if ( $outtemplate eq '' ) {
$outtemplate = 'temp%08d.TSV';
}

open( DATAFILE, $datafilename ) or die "unable to open $datafilename as datafile\n";


# First, read first line. It's TAG-NAME list.

our @tagname;
our $line;

$line = <DATAFILE>;
@tagname = split /\t/, $line;
for ( $i = 0; $i < scalar( @tagname ); $i++ ) {
$tagname[$i] =~ s/^\s*(".*")\s*$/\1/;
}


# Okey. Now let's read each rows.
# First row is not numeral, but is time of when data was sampled. so skip first row.

our $filecounter = 0;
for ( $row = 0; $row < scalar( @tagname ); $row += $at_once ) {
debug( "$row start\n" );
# bring file pointer to top of line.
seek( DATAFILE, 0, 0 );

# skip tagname line.
$line = <DATAFILE>;

my @output;
my @data;
my @sum;
my @mean;
my @sqsum;
my @subpart;
my @datas;

for ( my $i = 0; $i < $at_once; $i++ ) {
$output[$i] = 1;
}

while (<DATAFILE>) {
my @oneline = split /\t/, $_;
my $val;

for ( my $i = 0; $i < $at_once; $i++ ) {
$oneline[$row + $i] =~ s/^\s*"(.*)"\s*$/\1/;
$val = $oneline[$row + $i];
if ( $val eq '' ) {
$output[$i] = 0;
} else {
push @{$data[$i]}, $val;
$sum[$i] += $val;
}
}
}
debug( "read data\n" );

# calculate $mean and $subpart.

for ( my $i = 0; $i < $at_once; $i++ ) {
if ( $output[$i] ) {
$datas[$i] = scalar( @{$data[$i]} );
$mean[$i] = $sum[$i] / $datas[$i];
if ( $mean[$i] eq 'NaN' ) {
$output[$i] = 0;
next;
}

for ( my $j = 0; $j < $datas[$i]; $j++ ) {
my $val;
$val = $data[$i][$j] - $mean[$i];
$sqsum[$i] += $val * $val;
}
$subpart[$i] = sqrt( $sqsum[$i] );

if (( $subpart[$i] eq 'NaN' )||( $subpart[$i] == 0.0 )) {
$output[$i] = 0;
}
}
}
debug( "calc data\n" );

# dumpout the row.
for ( my $i = 0; $i < $at_once; $i++ ) {
my $filename = sprintf( $outtemplate, $filecounter );
if ( !defined( $tagname[$row+$i] )){
next;
}

open( OUTPUT, "> $filename" ) or die "unable to open $filename as output file. Template was $outtemplate.\n";
print OUTPUT "tagname\t$tagname[$row+$i]\n";
print OUTPUT "Correlation\t" . ( $output[$i] ? 'OK' : 'NG' ) . "\n";
print OUTPUT "sum\t$sum[$i]\n";
print OUTPUT "mean\t$mean[$i]\n";
print OUTPUT "sqsum\t$sqsum[$i]\n";
print OUTPUT "subpart\t$subpart[$i]\n";
print OUTPUT "datas\t$datas[$i]\n";
print OUTPUT "\n";

foreach ( @{$data[$i]} ) {
print OUTPUT "$_\n";
}

close OUTPUT;
debug( "output $filename \n" );

$filecounter++;
}

ROWSTEP_CLEANUP:
undef @output;
undef @data;
undef @sum;
undef @mean;
undef @subpart;
undef @datas;
}

close DATAFILE;

sub debug {
my $time = gmtime();
print STDERR "$time: ";
foreach ( @_ ) {
print STDERR $_;
}
}

このプログラム中、唯一判らないのは「$at_once」だろう。これはようするに、読んだデータの内、$at_once 個のデータ列づつ平均値だのなんだのを計算して、各データ列ごとに別のファイルに出力する、という事だ。

実はこのプログラム、かなりメモリを消費する。データファイル自体は1行づつ読んではパースし、不要な部分は捨てているのだが、元のデータが 27000列とかだと、一日分のデータだけでも 32bit Windows システムではメモリが足りずに途中でハングする。そこで、一度に処理する行数をスクリプト中に書いておくことにした。メモリが足りないならここを調節すればよい。

ただし、可能な限り大きな値を指定しておくことをお勧めする。実験として50を選んであるが、ものすごく遅くて難儀した。27000の場合、540回同じファイルを読み直すことになり、そのたびにファイル全体を文字列処理する必要があるのだから、当たり前と言えば当たり前だが。

2009年2月4日

リファクタリング-3-

Math::NumberCruncherのコードの一部を手元に展開した状態で読んでいく…ん? んん?? んんん~~~???

どうみても、Correlation() の計算式は
ピアソン積率相関係数 のそれではないっ!!!

確かに似たような値が出てきてはいるが、明らかに違うっ!!!

ぬぅ。なんということだ。大前提からしてやり直しかっ!!!

しかも。これは致命的な問題ではないからいいようなものの…同モジュールに含まれている平方根を出す関数 SqrRoot() は

0.0 を入れると NaN を返すっ!!!
まてぃぃいいいいい!!!

どこの世界に0.0 の平方根が計算できない数値演算ライブラリがあるっ!!! ちなみに、他の値を入れると(プラスの値であれば)妥当な出力を出してくる。どうやら、0.0 の場合におかしくなるような漸近式を使っているらしい。

まぁ、0.0だろうが NaN だろうが、その結果を分母に持ってきて分数を計算すれば、NaNになるのは変わらないので、そりゃ結果だけ見れば構わないと言えば構わないのだが…。とりあえず、この程度のデバッグもできていないモジュールである以上、これは信頼するわけには行かぬ。

というわけで根こそぎ作り変えてくれよう。
!!決定!!

とりあえず、次のようなデザインにする。
  1. まず、データ列を1データ列1ファイルに分割する。
  2. そのデータ列には、当該データ列の平均値、平均値と各データの二乗和、さらに二乗和の平方根、の3つは最低限でも、データとして記録する。
  3. できればデータの個数も記録する。
  4. もちろん、ラベル名も記録する。
  5. いうまでもなく、データ列の値そのものも1行1エレメントで記録する。
  6. 2 で得られた値のどれかが NaN になるか、あるいは「二乗和の平方根」が 0.0 になる場合は、そのようなデータ列は当該データファイルを作らない。
  7. このファイルはすべて文字列で数値を記録する。
で、このファイルを複数読み込んで、組み合わせ毎に計算するプログラムを別途作る。各データ列ごとの事前計算は全部記録されているので、本来の計算に集中できる…に違いない。

…それにしてもここまで巻き戻るとは…

2009年2月1日

リファクタリング-2-

リファクタリングとして数多くのことをする必要があるが、とりあえず最初にやるべきことは、Math::NumberCruncher のコードから、今回必要なルーチンをコピーして持ってくることだろう。Correlation()関数を呼び出す回数は O(n2) になるが、Wikipediaの「相関係数」のページによると相関係数を求める式はこのようになっている:

分母が特に「列」単位で独立して計算できる事がわかる。列単位ならば計算はO(n)で済む。このような最適化がかけられるかどうか、調べるには元のソースが無くてはいけない。
幸い、CPANには perl のソースが .pm ファイルとしてついてくる。それをコピーしてみたが、全部 perl で書かれているようだ。これならば最適かもかけられよう。

次に、x と y のラベル名を読み込むコードを変更する。旧来:
our $i = 0;
our @x_tagname = ();
while (<COMPARE_X>) {
$_ =~ s/^\"//;
$_ =~ s/\"\n$//;
$x_tagname[$i] = $1;
$i++;
}
close COMPARE_X;
となっていたのだが、

  1. push()関数を使えばインデックスの $i は不要になる。

  2. ラベル名に " がついたままでもいいじゃないか。どうせ x, y だけでなくデータのラベルも " がついたままなのだから。文字列比較の際に " がついたまま比較すればいい

と考えるた。結果、このように簡単になる:
our @x_tagname = ();
while (<COMPARE_X>) {
$_ =~ s/^\s*(".*")\s*$/\1/;
push @x_tagname, ( $_ );
}
close COMPARE_X;
同じ変更は COMPARE_Y を読み込む部分についても行う。DATAFILEの最初の1行目を読んだ部分に関してはもっと極端だ。
# Read first line of .
# 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;
}
というこれは
$line  = <DATAFILE>;
@tagname = split /\t/, $line;
for ( my $i = 0; $i < scalar( @tagname ); $i++ ) {
$tagname[$i] =~ s/^\s*(".*")\s*$/\1/g;
}
とできる。

次に。今回のリファクタリングで、BestFit() を呼び出し、回帰直線の傾きと切片を求めるのをやめる。この計算も結構な高コストである上に、相関係数を求めた結果として、重要と思われたものについてのみ必要な数値だからだ。これらは、計算すべきターゲットが見つかってから計算するのでも間に合う。

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倍時間がかかる。只でさえ遅いのに…。

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

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

2009年1月2日

今年はエロで行きましょう!!

あけましておめでとうございます。今年もよろしくお願いいたします。


MS-IMEには「IMEパッド -手書き-」という機能があります。マウスとかで文字を描くと、対応する漢字と思しきものの候補を出してくれる、という「字が読めない」私にはとても便利な代物です。

さて、このIMEパッドに新年の書初めを。ええ、縁起のいい文字を。とりあえず。これ。

はい。


エロ


でございます。さて、どのような文字に認識されるでしょうか?!


次のようになりました。


思った以上に縁起が良い。





とでました。こりゃ面白い。調子に乗って他のパターンも入れてみます。


エロは「扱う」に限るそうです。エロは吉でもありましたから、これは


今年、景気を良くしたかったらエロを解放せよ


というお告げに違いありません(Microsoft の??)


じゃ、次。「ツンデレ」行ってみましょう。

んー、ちょっと難しすぎたかな。混乱しているようです。もう少し簡単にするために「デ」を「テ」にしてみましょう。



!!栄える!!



ツンデレ、今年も栄えますか…つーかちょっと無理ありすぎです。次行ってみよう!!



あぁっっっ!!!


今年もロリ、受難!!!


只、叩かれるのみのようです。

と言うわけで結論!! 今年のこの突如として吹き荒れる大不況、脱出するには:
  1. エロ解放は絶対条件
  2. ツンデレ属性は念のためにつけておけ
  3. ロリはやめておけ。せいぜい女子校生からにしておけ。若奥様なんか狙い目だ。

こんなんでました~~