問題設定
下記のように文字列を転置することを考えます。
cat input.txt
AAA
BBB
CCC
cat output.txt
ABC
ABC
ABC
小さい行列の場合
まず手始めに小さい行列を転置します。
A-Cの3行で、それぞれ3文字を格納した行列を作製します。
seq_len=3
awk -v seq_len="${seq_len}" \
'BEGIN {A=65; C=A+3; for (i=A; i<C; i++){
alphabet=sprintf("%c", i)
s=sprintf("%"seq_len"s","")
gsub(/ /,alphabet,s)
print s}
}' \
> input1.txt
cat input1.txt
AAA
BBB
CCC
それでは転置を行います。
Stack OverflowにAWKのコードがありました。
連想配列を巧みに扱ったエレガントなコードです。
拝借します。
awk -F "" '
{for (i=1; i<=NF; i++) { a[NR,i] = $i }}
NF>p { p = NF }
END { for(j=1; j<=p; j++) {
str=a[1,j]
for(i=2; i<=NR; i++){ str=str""a[i,j] }
print str}}' input1.txt \
> output1.txt
cat output1.txt
ABC
ABC
ABC
なにも問題ありませんね
大きい行列の場合:メモリ消費量が尋常ではない
それでは大きいサイズの行列を転置します。
A-Zの26行で、それぞれ1,000,000文字を格納した行列を作製します。
seq_len=1000000
awk -v seq_len="${seq_len}" \
'BEGIN {A=65; Z=A+26; for (i=A; i<Z; i++){
alphabet=sprintf("%c", i)
s=sprintf("%"seq_len"s","")
gsub(/ /,alphabet,s)
print s}
}' \
> input2.txt
ファイルサイズは25Mです。
ls -lh input2.txt | cut -d " " -f 5
25M
それでは、これを上記のコードで計算します。
その前に、計算時間とメモリ消費量を記録するコマンドを作ります。
alias time_='/usr/bin/time -f "========\nreal %es\nuser %Us \nsys %Ss \nmemory:%MKB \n========"'
では、実行します。
time_ awk -F "" '
{for (i=1; i<=NF; i++) { a[NR,i] = $i }}
NF>p { p = NF }
END { for(j=1; j<=p; j++) {
str=a[1,j]
for(i=2; i<=NR; i++){ str=str""a[i,j] }
print str}}' input2.txt \
> output2.txt
========
real 25.33s
user 23.87s
sys 1.44s
memory:7554988KB
========
メモリをなんと7.6GBも消費しています!!
解決法
メモリ消費量が大きい理由は、連想配列に全データを格納しているからです。
なので、目的の計算が終わったら直ちにファイルに書き出すようにします。
nr=$(cat input2.txt | wc -l)
time_ awk -v nr="${nr}" -F "" \
'{for(i=1; i<=NF; i++){
row[i]=row[i] $i
if(NR==nr) print row[i]
}
}' input2.txt \
> output3.txt
========
real 6.46s
user 6.36s
sys 0.09s
memory:240704KB
========
メモリ消費量240MB!!
計算時間はおよそ1/4、メモリ消費量にいたっては約3/100になりました!
最後に、Stack Overflowのコードと今回のコードで出力結果が同じか確認します。
[ -z $(diff output2.txt output3.txt) ] &&
printf "同じ結果です\n"
同じ結果です
同じ結果が得られました。
学んだこと
大きなファイルを扱う場合、連想配列に格納したデータはEND
まで保持せず、すぐに出力しましょう