git rebaseマスターガイド - 履歴を美しく保つテクニック
gitの履歴がぐちゃぐちゃで恥ずかしい...そんな悩みを解決!インタラクティブリベースを使って、プロフェッショナルなコミット履歴を作る方法を実例とともに解説します。
「このスクリプト、なんでこんなに遅いの...」大量のファイル処理で悩んでいませんか?パイプライン最適化、並列処理、そして意外と知られていない高速化テクニックを実例で解説します!
シェルスクリプトを書いていて「なんか遅いな…」と感じたこと、ありませんか?
私は以前、10 万個のログファイルからエラーを抽出するスクリプトを書いたとき、処理に 30 分以上かかって頭を抱えました。でも、ちょっとした工夫で、なんと 2 分まで短縮できたんです!
今日は、そんな経験から学んだシェルスクリプトの高速化テクニックを共有します。
高速化の第一歩は「どこが遅いのか」を知ること。私がよく使う計測方法を紹介します:
# 時間計測の基本
time ./your_script.sh
# より詳細な計測
#!/bin/bash
start_time=$(date +%s.%N)
# ここに処理を書く
end_time=$(date +%s.%N)
elapsed=$(echo "$end_time - $start_time" | bc)
echo "処理時間: ${elapsed}秒"
これが一番効果的です。シェルスクリプトのループは想像以上に遅い!
# 10万ファイルで約10分かかる処理
find . -name "*.log" | while read file; do
grep "ERROR" "$file" >> errors.txt
done
# 同じ処理が約30秒で完了!
find . -name "*.log" -print0 | \
xargs -0 -P 8 grep -h "ERROR" >> errors.txt
-P 8
で 8 プロセス並列実行。CPU コア数に合わせて調整してください。
シェルスクリプトでは、外部コマンドの起動コストが意外と大きいんです。
# ファイルごとにsedを起動(遅い!)
for file in *.csv; do
sed 's/,/\t/g' "$file" > "${file%.csv}.tsv"
done
# awkで全ファイルを一度に処理
awk 'BEGIN{OFS="\t"} {gsub(/,/, "\t"); print > (FILENAME ".tsv")}' *.csv
パイプラインの順番を変えるだけで、劇的に速くなることがあります。
# 巨大なファイルを全部読んでから絞り込み
cat huge_file.log | grep "ERROR" | awk '{print $1, $3}' | sort | uniq
# grepで先に絞り込んでデータ量を減らす
grep "ERROR" huge_file.log | awk '{print $1, $3}' | sort -u
さらに、sort -u
は sort | uniq
より高速です!
一時ファイルを作らずに複数の処理を並列実行できます。
# 一時ファイルを使う場合(遅い)
grep "pattern1" bigfile > temp1.txt
grep "pattern2" bigfile > temp2.txt
diff temp1.txt temp2.txt
rm temp1.txt temp2.txt
# プロセス置換を使う場合(速い!)
diff <(grep "pattern1" bigfile) <(grep "pattern2" bigfile)
Bash の組み込みコマンドは外部コマンドより圧倒的に高速です。
# 外部コマンド(遅い)
filename=$(echo "$path" | sed 's/.*\///')
extension=$(echo "$filename" | sed 's/.*\.//')
# Bashの組み込み機能(速い!)
filename="${path##*/}"
extension="${filename##*.}"
# パス操作
path="/home/user/document.pdf"
dir="${path%/*}" # /home/user
file="${path##*/}" # document.pdf
name="${file%.*}" # document
ext="${file##*.}" # pdf
# 置換
text="hello world"
echo "${text/world/universe}" # hello universe
echo "${text//l/L}" # heLLo worLd
GNU Parallel を使えば、もっと簡単に並列化できます:
# インストール
# Ubuntu/Debian: sudo apt install parallel
# Mac: brew install parallel
# 基本的な使い方
find . -name "*.jpg" | parallel -j 8 convert {} {.}_thumb.jpg
# プログレスバー付き
ls *.mp4 | parallel --progress -j 4 ffmpeg -i {} -c:v libx264 {.}_compressed.mp4
実際のログ解析スクリプトを最適化してみましょう。
#!/bin/bash
# slow_log_analyzer.sh
for logfile in /var/log/app/*.log; do
echo "Processing $logfile..."
# エラーを抽出
grep "ERROR" "$logfile" > temp_errors.txt
# IPアドレスを抽出してカウント
while read line; do
ip=$(echo "$line" | grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
echo "$ip" >> all_ips.txt
done < temp_errors.txt
done
# 集計
sort all_ips.txt | uniq -c | sort -nr > ip_counts.txt
rm temp_errors.txt all_ips.txt
#!/bin/bash
# fast_log_analyzer.sh
# 並列処理でエラー行からIPを抽出して集計
find /var/log/app -name "*.log" -print0 | \
xargs -0 -P $(nproc) grep -h "ERROR" | \
grep -oE '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | \
sort | uniq -c | sort -nr > ip_counts.txt
なんと 20 倍の高速化!ポイントは:
nproc
で CPU コア数を自動取得)大量のデータを扱うときは、メモリ使用量も重要です。
# メモリを大量に使う(全データをソート)
cat huge_file | sort | uniq > result.txt
# メモリ効率が良い(部分的にソート)
sort -S 1G huge_file | uniq > result.txt # 1GBのメモリ制限
# さらに効率的(外部ソート)
LC_ALL=C sort huge_file | uniq > result.txt # ロケール無効化で高速化
スクリプトのボトルネックを見つける方法:
#!/bin/bash
# debug_profile.sh
# デバッグモードON
set -x # 実行するコマンドを表示
PS4='+ $(date "+%s.%N") ${BASH_SOURCE}:${LINENO}: ' # タイムスタンプ付き
# ここに処理を書く
find . -name "*.txt" -print0 | xargs -0 grep "pattern"
# デバッグモードOFF
set +x
最後に、私が使っている高速化チェックリストを共有します:
xargs
や parallel
を使える?sort
や grep
に LC_ALL=C を付けた?(日本語不要なら)シェルスクリプトの高速化は、ちょっとした工夫の積み重ねです。特に「ループを避ける」「並列処理する」の 2 つを意識するだけで、劇的に速くなることが多いです。
私の経験上、これらのテクニックを組み合わせれば、ほとんどのスクリプトで 5〜10 倍の高速化が可能です。30 分かかっていた処理が 3 分で終わるようになったときの爽快感…ぜひ体験してください!
次回は「開発者のための時短コマンド集」を紹介します。ripgrep や fd など、最新の CLI ツールで開発効率を爆上げしましょう!