pythonista も ls を読むべきか?
DESCRIPTION
Python Developers Festa 2013.11 での発表資料です。 https://github.com/pyspa/pyfes/blob/develop/201311.rst 性能計測結果は Solaris 系の OpenIndiana 151a 上で実施したものですので、他の OS の場合は異なる傾向となる可能性もあります。TRANSCRIPT
Pythonista も ls を読むべきか?
藤原 克則 @flyingfoozy
自己紹介
O 藤原 克則 (FUJIWARA Katsunori)
O ホームページ http://www.lares.dti.ne.jp/~foozy/index.ja.html
O ブログ http://d.hatena.ne.jp/flying-foozy/
O Twitter @flyingfoozy
O 分散リポジトリ型履歴管理ツール Mercurial の翻訳コミッタ/コントリビュータ
O Mercurial の hack に特化した なんちゃって Pythonista
O Solaris 上で稼働させる HPC 向けファイルシステムの実装などを 仕事でやっていたことも
O 「Tokyo OpenSolaris 勉強会」 で Solarisカーネルの実装に関する勉強とか http://www.opensolaris.gr.jp/study.html
O 下位レイヤの話も好き
執筆過程で見つけた バグの修正パッチが
ソースツリーに取り込まれた
GNU coreutils の ls は ワシが育てた! (約1/4900)
本日のお題は….
Pythonista も ls を読むべきか?
Pythonista: 「os.listdir() を使うから 読まなくても良くねぇ?」
例えばこんな感じ?
import os
for e in os.listdir(path):
fullpath = os.path.join(path, e)
if os.path.isdir(fullpath):
# ディレクトリに対する処理
elif os.path.isfile(fullpath):
# ファイルに対する処理
.....
そんな実装では スケールしない!!!
システムコール 呼び出しコスト
O os.path.isdir(), os.path.isfile() ... は 判定処理毎に lstat(2) 呼び出し
O 呼び出し頻度は 「要素数 × 平均的な if 判定数」
O コンテキストスイッチを生じるので 通常の関数呼び出しよりも 圧倒的にコストが高い
多少コスト意識が ある場合は….
import os, stat
for e in os.listdir(path):
fullpath = os.path.join(path, e)
st = os.lstat(fullpath)
if stat.S_ISDIR(st.st_mode):
# ディレクトリに対する処理
elif stat.S_ISREG(st.st_mode)
# ファイルに対する処理
.....
それでも残る 性能劣化要因
要因 (1) ~ os.lstat() 呼び出しコスト
O os.listdir() は 内部的に readdir(3) を使用
O readdir(3) が返す struct dirent は 要素種別情報 d_type を保持
O 種別情報のみで事足りる場合 os.lstat() 呼び出し自体が不要な筈 (サイズや日時情報が必要ない場合等)
要因 (2) ~ ファイル名解決コスト
O OS内部でのファイル名解決 (ファイル名⇒対象の管理情報)は パス要素毎の繰り返し処理で実現
O os.lstat() に指定されるファイル名にも 同様の解決コストが必要
“foo/bar/baz/file” に対する OS内部でのファイル名解決例
1. “.” への “foo” の問い合わせ
2. “./foo" 相当の管理情報の取得
3. “./foo” への “bar” の問い合わせ
4. “./foo/bar” 相当の管理情報の取得
5. “./foo/bar” への .......
ファイル名解決における 直接的なコスト
O ディレクトリ階層の深さ ⇒ 繰り返しによるコストの高低
O 配下要素の多寡 ⇒ 要素有無の確認コストの高低 O OS/ファイルシステム毎に色々な性能改善策
(名前解決キャッシュ/木構造管理/ハッシュテーブル)
O 運用回避 e.g.ディレクトリ配下の要素数に上限を設ける (proxyサーバやブラウザのキャッシュ)
ファイル名解決における 間接的なコスト
O 階層/配下要素の増加による I/O 量の増加
O メモリアクセスと比べて、 圧倒的にコストが高い
O I/O 対象の散在によるシークコストの増加
O 記録媒体が HDD であれば、 ミリ秒単位の I/O 待ち状態
O GHz クラスの CPU なら、 単純計算でも百万命令単位分の I/O 待ち
性能劣化の低減策
低減策 (1) ~ os.lstat() 呼び出しの低減
O readdir(3) が返す struct dirent には 各要素の種別情報を保持する d_type フィールドがある
O 種別情報のみで事足りるなら lstat(2) 呼び出し自体が不要
低減策 (2) ~ ファイル名解決コストの低減
O fstatat(2) システムコールの利用
O SYNOPSYS int fstatat(int dfd, const char *path, struct stat *buf, int flag);
O 起点ディレクトリ(dfd)からの相対的名前解決
O 直下の要素なら問い合わせは1階層限定
O I/O対象を局所化可能
低減策の効果計測
基本条件
O 1.8GHz 4 core Atom 環境
O I/O 要因での性能劣化を極力防止
O メモリファイルシステム上でテスト
O 常に現ディレクトリからの相対アクセス
O 必要なファイル/ディレクトリ要素のみ
lstat(2) 実行のコスト
O 実行条件
O 1000 要素を保持するディレクトリ
O 1000 要素それぞれへの lstat(2) 実施
O 計測結果
O 1000 回繰り返しで 3 ~ 4 秒の違い
O lstat(2) 実行コストは 1回あたり 3 usec ~ 4 usec
ファイル名解決のコスト
O 実行条件
O 現ディレクトリから 10 階層隔てる
O 1000 要素を保持するディレクトリ
O 1000 要素それぞれへの lstat(2) または fstatat(2) 実施
O 計測結果
O 1000 回繰り返し時に 6 ~ 7 秒の違い
O lstat(2) と fstatat(2) のコスト差は 1階層あたり 0.6 usec ~ 0.7 usec
性能計測の評価
O 対象10,000超で体感的な差が出る可能性
O システムコールのコスト差だけでも 対象1,000,000なら数秒オーダーの差
O 実際の環境では、性能劣化要因が増える
O ディレクトリ階層が複雑化した場合 ファイル名解決コストの増加
O HDD上のファイルシステムの場合 シーク発生による I/O待ちの増加
低減策の問題点
環境毎の仕様準拠
O dirent.d_type による種別情報の提供は POSIX 標準ではオプション扱い
O GNU coreutils の ls でも 使用可能な環境では使用
O fstatat(2) の標準化は XPG7 (2013) から
O XPG7 非準拠なら、サポートの必要無し (とは言うものの多くの環境で利用可能)
O GNU coreutils の ls では未使用
Python からの利用可能性
O Python の標準 API からは dirent.d_type も fstatat(2) も使えない
Python から使えないなら そんな話するな!
そんな時の為の C ライブラリ連携!
Mercurial における listdir() の独自実装
まーきゅりあるノ話ヲ シタイダケデハ ナイデスヨ?(棒)
O ディレクトリ配下要素の 情報取得を行う機能 osutil.listdir() をMercurialではCライブラリとして独自実装
O システムコール実行コストを 環境に応じて極力低減
O DT_REG マクロ判定 (dirent.d_type 利用の可否)
O AT_SYMLINK_NOFOLLOW マクロ判定 (fstatat(2) 利用の可否)
O Cライブラリ連携を使用できない環境向けに pure Python 版 osutil もあるが 性能的には当然Cライブラリ実装よりも遅い
結論
ls.c は読まなくても….
O mercurial/osutil.c の listdir() 実装は 読んでおいた方が良いかもね!
O ディレクトリ要素や要素毎情報の取得で 使用している API を把握しよう
O API 使い分けによる 性能への影響を理解しよう
O POSIX環境向けと Windows環境向け実装が 同一ファイル中にあるので参照時は注意
Pythonista も….
O 「lsを読まずにプログラマを名乗るな!」を 買ってね!
O OS の内部処理とかファイルシステム周りの話も盛り込んであるよ!
参考資料
Mercurial
O プロジェクトページ http://mercurial.selenic.com/
O mercurial/osutil.c http://selenic.com/repo/hg/file/stable/mercurial/osutil.c
O mercurial/pure/osutil.py http://selenic.com/repo/hg/file/stable/mercurial/pure/osutil.py
POSIX仕様
O XPG6: IEEE Std 1003.1, 2004 Edition http://pubs.opengroup.org/onlinepubs/9699919799/
O XPG7: IEEE Std 1003.1, 2013 Edition http://pubs.opengroup.org/onlinepubs/009695399/
GNU coreutils
O プロジェクトページ http://www.gnu.org/software/coreutils/
O src/ls.c http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c
「lsを読まずに プログラマを名乗るな!」 O 書籍情報
http://www.shuwasystem.co.jp/products/7980html/3943.html
O サポートページ http://www.lares.dti.ne.jp/~foozy/fujiguruma/ls-src/index.html
おしまい