見出し画像

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 上でのフラットファイルデータ処理の魅力です.

この記事が気に入ったらサポートをしてみませんか?