bash ≫ python ~= awk ≫ C
note 始め,気負ってタテマエ記事を連発しましたが,今後は素直に柔かい記事も書いていこうと思います.
IT弘法は筆を択ぶ
以前の記事で ALZETA の中ではフラットファイルデータを扱っていると書きました.
その時は,フラットファイルデータを扱うソフトウェアをざっくり「ツール」と呼称していました.「ツール」は別に何を選んでも構いませんが,「弘法筆を択ばず」と単純に言えないのが IT のおくゆかしいところです.ここでは簡単なお題を bash,awk,python, C (C はツール…とは言えないけれど)で解いて実行時間を比較してみました.
お題
100万件の売り上げデータに含まれる全商品ID(100件あります)を取り出す.データは以下のように,タブ区切りの3フィールド(日時,商品ID,数量)となっており,全件100万件レコードは商品IDでソートされています.
2021-01-02 12:57:05[TAB]商品02754556936[TAB]1
2021-01-03 22:13:47[TAB]商品02754556936[TAB]1
2021-01-04 16:31:51[TAB]商品02754556936[TAB]1
.
.
2021-01-06 15:56:09[TAB]商品02767771390[TAB]1
.
.
2021-01-07 02:49:39[TAB]商品03355622524[TAB]1
.
.
ここから,
商品02754556936
商品02767771390
商品03355622524
.
.
というデータを取り出すという問題です.データはファイル "nr1M_nk100" に格納されています.
bash の場合
最近の Linux で一番問答無用に使えるのは bash です.bash でこの処理を書いてみました.
※ note で "code" すると,空白行がなぜか詰められてしまうのですね… Gist を埋め込めば良いのですけど,今回はそこまですることもないかと思います…
#!/bin/bash
file=nr1M_nk100
SEP=$'\t'
while IFS="${SEP}" read -a fields -r line
do
if [ "${fields[1]}" != "${prev}" ]; then
echo "${fields[1]}"
prev="${fields[1]}"
fi
done < ${file}
# bottom of file
もっとスマートにかける!というご意見はおありかもしれませんが,ご容赦ください(以下同文).
これを実行してみると,その実行時間は 36.565秒.
$ time ./uniq_bash.sh > /dev/null
real 0m36.565s
user 0m33.794s
sys 0m2.772s
awk の場合
awk も歴史の長いツールで,むかーしの UNIX からずっと使えるものです.
#!/bin/bash
file=nr1M_nk100
awk -F '\t' 'BEGIN {prev = ""} { if (prev != $2) {print $2; prev = $2} }' ${file}
# bottom of file
awk は直接コマンドラインから起動もできますが,今回は上記のように awk をラップする bash スクリプトの形にしています(前の例と違い,bash は awk を起動するだけで,データには一切触らないことに注意)
これを実行してみると,その実行時間は 0.958秒.
$ time ./uniq_awk.sh > /dev/null
real 0m0.958s
user 0m0.943s
sys 0m0.015s
全然違いますね!
python の場合
bash だ awk だと叫んでばかりだと,どこのロートルさんですか?と言われそうなので,比較的新しい python も.
#!/usr/bin/env python3
filename = "nr1M_nk100"
f = open(filename, 'r')
prev = ""
while 1:
line = f.readline()
if line == "":
break
if line.split('\t')[1] != prev:
print(line.split('\t')[1])
prev = line.split('\t')[1]
f.close()
# bottom of file
実行時間は,1.022秒.
$ time ./uniq.py > /dev/null
real 0m1.022s
user 0m1.006s
sys 0m0.016s
C の場合
でもやっぱりロートルなので,C でも書きます.(最近は getline() も POSIX 認定されて便利な世の中です…)
/* uniq.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char filename[] = "nr1M_nk100";
void main () {
FILE *f = fopen(filename, "r");
char *line = NULL;
size_t linesize;
char *prev = NULL;
size_t prev_len = 0;
while (getline(&line, &linesize, f) >= 0) {
char *chrptr, *delim;
char *merchandise_id;
size_t merchandise_id_len;
chrptr = line;
delim = memchr(chrptr, (int)'\t', linesize);
chrptr = (merchandise_id = delim + 1);
delim = memchr(chrptr, (int)'\t', linesize);
merchandise_id_len = delim - merchandise_id;
if (merchandise_id_len != prev_len || memcmp(prev, merchandise_id, merchandise_id_len) != 0) {
fwrite(merchandise_id, 1, merchandise_id_len, stdout);
fwrite("\n", 1, 1, stdout);
if (prev_len < merchandise_id_len) {
char *tmp = (char *) realloc(prev, sizeof(char) * merchandise_id_len);
if (tmp == NULL) {
perror("realloc failed.");
exit(1);
}
prev = tmp;
}
memcpy(prev, merchandise_id, merchandise_id_len);
prev_len = merchandise_id_len;
}
}
fclose(f);
}
/* bottom of file */
C の場合,コンパイルしないといけません…
$ gcc -o uniq uniq.c
実行してみると,0.092秒.
$ time ./uniq > /dev/null
real 0m0.092s
user 0m0.081s
sys 0m0.011s
というわけで,
bash (36秒) >> python, awk (1秒) >> C (0.1秒)
という結果になりました.今回はデータが100万件ぐらいでしたので,bash でもこのくらいで済んでいますが,データが1億件になると,
bash (1時間) >> python, awk (1分40秒) >> C (10秒)
ということになるので,かなり業務遂行時間に影響が出てきます.
ここまでで言えることは,処理の内容を問わず,bash でデータを直接取り扱うのはやめた方が良さそうです.
また,コードの量からすると,一番コストパフォーマンスの良いのは awk ということになるでしょうか.C はやはりコーディングの量が多くなってしまうのと,ちょっとした変更(例えば,取り出すフィールドの位置が変わってしまったり,とか…それを吸収するために引数を使うとすると,その引数を扱うコードが数行増えます)をするにも面倒です.
python も,ファイルを読むのに open という儀式が必要だったりします.もちろん,awk では不可能な高機能さとデータ処理におけるライブラリの充実が python の魅力ですので,集計や統計処理を行うには python の方が便利/python でなければできない,といったこともたくさんあります.
というわけで,我々はデータの仕事をするときに awk を使うことが多いです.ALZETA GUI を使わずに CLI で仕事をする時でも,awk を使えば「中間ファイルプレビュー」を行うのと同じ手軽さでデータを動かすことができます,ALZETA の元となっている ETLanTIS データ処理エンジン内でも,各所で awk を使用しています.ただし,速度が重要なところは C やその他の言語で機械語プログラムを作成しています.処理(に要求される速度,柔軟性)に応じて python,awk,独自プログラムを組み合わせて自由にデータ処理を定義できるところが,Linux 上でのフラットファイルデータ処理の魅力です.