仕事の中でシェルスクリプトの性能改善をすることになった。そのための性能改善をするための事前調査という形で、
- 想定しうる実装
- 実装の違いによる性能差
これを確認しよう、というもの。
Oracleの監査XMLが16万ファイル……
OracleのXML監査の集約を行う処理をシェルで実装。
で、出力されてきた監査が16万ファイルもあってえらーーーーーーーく処理に時間がかかる。出力されたファイルを転送する時間が決まっているため、その時間までに処理を終わらせなければならない。いざ、性能改善! という次第。
比較する実装
- 今の実装
ファイルを入力リダイレクトしてwhile
とread
でやりくりする方法
while read LINE
do
# あれやこれや
done < ファイル
- 新しい実装
もう全部、awk
でいいんじゃね?
検証するコード
16万ファイルを実際に作るものあれなので、
16万ファイル × 5レコード分 + α = 100万レコード
という感じでXMLのレコードが100万件のダミーデータファイル1つを入力にする。
その中のuser
タグの中にmarkus
またはjacob
という文字列を持つ情報を抽出する。
といった塩梅。詳しくは最後に載せるテスト用コードを参照。
結果
パターン | 所要時間(sec) |
---|---|
while +read
|
15381 |
awk |
8 |
awkすげーーーーーーーー!
検証用コード
上に書いていないこともいろいろやっているコード。
test_performance.sh
#!/bin/bash
#
gen_records=$1
# 第1引数の出力レコード数がなかったら、10万レコードとする。
if [ -z ${gen_records} ]; then
gen_records=100000
fi
TEST_FILE="testdata.xml"
TEST_TARGET="testTarget.dat"
# 検証に使用するデータを用意する。
function prepare(){
max_gen=$1
# create test XML data
echo '<?xml version="1.0" encoding="utf-8">' > ${TEST_FILE}
for i in $(seq 1 ${max_gen})
do
printf "%s%s%s%s%s%s%s%s%s\n" \
"<record>" \
"<keyset><key>${i}</key><key>none</key></keyset>" \
"<user>kevin</user>" \
"<sequence>" \
"<data>fix</data><data>fix</data><data>fix</data><data>fix</data><data>fix</data><data>fix</data>" \
"<data>value_${i}</data><data>value_2</data>" \
"</sequence>" \
"<Action>${i}</Action>" \
"</record>" >> ${TEST_FILE}
done
i=100001
printf "%s%s%s%s%s%s%s%s%s\n" \
"<record>" \
"<keyset><key>${i}</key><key>none</key></keyset>" \
"<user>markus</user>" \
"<sequence>" \
"<data>fix</data><data>fix</data><data>fix</data><data>fix</data><data>fix</data><data>fix</data>" \
"<data>value_${i}</data><data>value_2</data>" \
"</sequence>" \
"<Action>100</Action>" \
"</record>" >> ${TEST_FILE}
i=100002
printf "%s%s%s%s%s%s%s%s%s\n" \
"<record>" \
"<keyset><key>${i}</key><key>none</key></keyset>" \
"<user>markus</user>" \
"<sequence>" \
"<data>fix</data><data>fix</data><data>fix</data><data>fix</data><data>fix</data><data>fix</data>" \
"<data>value_${i}</data><data>value_2</data>" \
"</sequence>" \
"<Action>101</Action>" \
"</record>" >> ${TEST_FILE}
# create test target user list
{
echo "markus"
echo "jacob"
} > ${TEST_TARGET}
}
# whileを使ったループで全件抽出
function perform_while_all(){
local -r output="testresult_for.xml"
echo -n "" > ${output}
while read file_line
do
printf "%s\n" ${file_line} >> ${output}
done < ${TEST_FILE}
}
# whileを使ったループでuserタグのテキストがmarkusのみを抽出
function perform_while_markus(){
local -r output="testresult_for.xml"
echo -n "" > ${output}
while read file_line
do
availablity=$(echo "${file_line}" | grep '<user>\(.*\)</user>' | wc -l )
if [ 0 -eq ${availablity} ]; then
continue
fi
user=$(echo "${file_line}" | sed -e 's|^.*<user>\(.*\)</user>.*$|\1|g' )
isMarkus=$(grep ${user} ${TEST_TARGET} | wc -l )
if [ 0 -eq ${isMarkus} ]; then
continue
fi
printf "%s\n" ${file_line} >> ${output}
done < ${TEST_FILE}
}
# awkを使ったループで全件抽出
function perform_awk_all(){
local -r output="testresult_awk.xml"
echo -n "" > ${output}
awk -v output=${output} '
{
print $0 >> output
}
' ${TEST_FILE}
}
# awkを使ったループでuserタグのテキストがmarkusのみを抽出
function perform_awk_markus(){
local -r output="testresult_awk.xml"
echo -n "" > ${output}
awk -v matchfile=${TEST_TARGET} '
# 対象ユーザ一覧を取得
BEGIN {
matchlist = "|";
CAT = "cat " matchfile;
while ((CAT | getline) > 0) {
matchlist = matchlist "" $0 "|";
}
close(CAT)
}
# メイン処理
{
user = $0
sub(/^.*<user>/, "", user)
sub(/<\/user>.*/,"", user)
item = "|" user "|"
if (index(matchlist, item) > 0) {
print $0
}
}
' ${TEST_FILE} >> ${output}
}
# awkを使ったループでuserタグのテキストがmarkusのみを抽出。
# しかもパターンとしてActionタグのテキストが100または101の場合のみ処理する。
function perform_awk_markus_withAction(){
local -r output="testresult_awk2.xml"
echo -n "" > ${output}
awk -v matchfile=${TEST_TARGET} '
# 対象ユーザ一覧を取得
BEGIN {
matchlist = "|";
CAT = "cat " matchfile;
while ((CAT | getline) > 0) {
matchlist = matchlist "" $0 "|";
}
close(CAT)
}
# メイン処理
$0 ~ /<Action>10[01]<\/Action>/{
user = $0
sub(/^.*<user>/, "", user)
sub(/<\/user>.*/,"", user)
item = "|" user "|"
if (index(matchlist, item) > 0) {
print $0
}
}
' ${TEST_FILE} >> ${output}
}
# gawkを使ったループでuserタグのテキストがmarkusのみを抽出。
function perform_gawk_markus(){
local -r output="testresult_gawk.xml"
echo -n "" > ${output}
gawk -v matchfile=${TEST_TARGET} '
# 対象ユーザ一覧を取得
BEGIN {
matchlist = "|";
CAT = "cat " matchfile;
while ((CAT | getline) > 0) {
matchlist = matchlist "" $0 "|";
}
close(CAT)
}
# メイン処理
{
user = $0
sub(/^.*<user>/, "", user)
sub(/<\/user>.*/,"", user)
item = "|" user "|"
if (index(matchlist, item) > 0) {
print $0
}
}
' ${TEST_FILE} >> ${output}
}
# gawkを使ったループでuserタグのテキストがmarkusのみを抽出。
# しかもパターンとしてActionタグのテキストが100または101の場合のみ処理する。
function perform_gawk_markus_withAction(){
local -r output="testresult_awk2.xml"
echo -n "" > ${output}
gawk -v matchfile=${TEST_TARGET} '
# 対象ユーザ一覧を取得
BEGIN {
matchlist = "|";
CAT = "cat " matchfile;
while ((CAT | getline) > 0) {
matchlist = matchlist "" $0 "|";
}
close(CAT)
}
# メイン処理
$0 ~ /<Action>10[01]<\/Action>/{
user = $0
sub(/^.*<user>/, "", user)
sub(/<\/user>.*/,"", user)
item = "|" user "|"
if (index(matchlist, item) > 0) {
print $0
}
}
' ${TEST_FILE} >> ${output}
}
echo "-------------PERFORMANCE TEST--------------------"
echo "##PREPARE##"
prepare ${gen_records}
echo "records: $(wc -l ${TEST_FILE}) lines"
echo "kevin records: $(grep kevin ${TEST_FILE} | wc -l)"
echo "markus records: $(grep markus ${TEST_FILE} | wc -l)"
echo "Action 100 OR 101 records: $(grep -E '<Action>10[01]<\/Action>' ${TEST_FILE} | wc -l)"
echo "##PREPARE FINISH##"
echo "##EXAMINE##"
SECONDS=0
perform_while_all
echo "perform_while_all:Performance time: ${SECONDS} sec"
SECONDS=0
perform_awk_all
echo "perform_awk_all:Performance time: ${SECONDS} sec"
SECONDS=0
perform_while_markus
echo "perform_while_markus:Performance time: ${SECONDS} sec"
SECONDS=0
perform_awk_markus
echo "perform_awk_markus:Performance time: ${SECONDS} sec"
SECONDS=0
perform_gawk_markus
echo "perform_gawk_markus:Performance time: ${SECONDS} sec"
SECONDS=0
perform_awk_markus_withAction
echo "perform_awk_markus_withAction:Performance time: ${SECONDS} sec"
SECONDS=0
perform_gawk_markus_withAction
echo "perform_gawk_markus_withAction:Performance time: ${SECONDS} sec"
echo "-------------PERFORMANCE TEST--------------------"