この話題を取り上げ始めたのは、
過去2回では、
依存関係情報収集スクリプトとSQLiteの罠
前回作成した2つのスクリプトをまとめて、
さっそく、
気軽に使えるのが特徴だとしても、
この問題はSQLiteのホームページのFAQにも言及されていました。
[原文]
- (19) INSERT is really slow - I can only do few dozen INSERTs per second
Actually, SQLite will easily do 50,000 or more INSERT statements per second on an average desktop computer. But it will only do a few dozen transactions per second. Transaction speed is limited by the rotational speed of your disk drive. A transaction normally requires two complete rotations of the disk platter, which on a 7200RPM disk drive limits you to about 60 transactions per second.
Transaction speed is limited by disk drive speed because (by default) SQLite actually waits until the data really is safely stored on the disk surface before the transaction is complete. That way, if you suddenly lose power or if your OS crashes, your data is still safe. For details, read about atomic commit in SQLite..
By default, each INSERT statement is its own transaction. But if you surround multiple INSERT statements with BEGIN...
COMMIT then all the inserts are grouped into a single transaction. The time needed to commit the transaction is amortized over all the enclosed insert statements and so the time per insert statement is greatly reduced.
[拙訳]
- (19)インサートがきわめて遅い - 1秒あたり数十件のインサートしか行えない
実のところ、
SQLiteは、 平均的なデスクトップPC上で50,000件/秒以上のインサートを容易にこなしますが、 トランザクション処理が1秒あたり数十件しかできないのです。トランザクションの速度はHDDの回転速度に依存します。トランザクションには、 通常、 HDDの記録面が2周する必要があります。そのため7200RPMのHDDでは (1分あたり7200回転=1秒あたり120回転なので) 1秒あたりに可能なトランザクションの回数は60回に制限されます。 トランザクションの速度がディスクドライブの速度に制限されるのは、
SQLiteは (通常では) データが正しくディスク上に書き込まれたことを確認するまで、 処理の完了を待つためです。こうしておけば、 突然電源が切れたり、 OSがクラッシュしたとしてもデータは安全ですから。詳細については、 SQLiteのatomic commitについて調べてください。 デフォルトの設定では、
インサートはそれぞれがトランザクション処理になります。しかし、 複数のインサートをBEGIN... COMMITで囲めば、 その中のインサートは1つのトランザクションにまとめられ、 その結果、 各インサートにかかる時間を大幅に改善することが可能です。
なるほど、
上記FAQによると、
そこで、
def insert_db2(dbname, t):
conn = sqlite3.connect(dbname)
try:
print "inserting ", t
conn.executemany('insert into depends values(?, ?, ?, ?)', t)
conn.commit()
except sqlite3.Error, e:
print "An error occurred:", e.args[0]
conn.rollback()
insert_
list = []
for file in files:
base = os.path.basename(file)
tmp = get_depends(file)
for i in tmp:
(soname, realname) = split_parts(i)
print("{0}, {1}, {2}, {3}".format(base, file, soname, realname))
t = (base, file, soname, realname)
# insert_db(dbname, t)
list.append(t)
insert_db2(dbname, list)
この改造でどれくらい処理速度が向上するかを確認するために、
1つずつinsert | まとめてinsert | |
---|---|---|
実時間 | 38m4. | 0m27. |
ユーザ時間 | 0m13. | 0m5. |
システム時間 | 0m19. | 0m5. |
登録処理が終了したデータベースを調べると、
この結果を見ると、
一方、
これらの改造の結果、
このスクリプトの完成版は筆者の日記のページに添付ファイルとして置いています。同じようなコードを再掲するのも何なので、
依存関係情報検索スクリプト
依存関係情報のデータベースができたので、
前回紹介したように、
コマンド名 | ファイル名 | 共有ライブラリ名 | ライブラリへのパス |
---|---|---|---|
sed | /usr/ | linux-vdso. | |
sed | /usr/ | libacl. | /usr/ |
sed | /usr/ | libc. | /lib64/ |
sed | /usr/ | libattr. | /usr/ |
sed | /usr/ | /lib64/ |
検索ツールでは、
検索の方向は、
前回紹介したように、
コマンド名 | ファイル名 | 共有ライブラリ名 | ライブラリへのパス |
---|---|---|---|
base | path | soname | realname |
Pythonには、
1 import getopt
2
3 def get_opts():
4 try:
5 opts, args = getopt.gnu_getopt(sys.argv[1:], "b:p:s:r:", ["base=","path=","soname=", "realname="])
6 except getopt.GetoptError:
7 usage()
8 sys.exit(2)
9
10 for o, a in opts:
11 if o in ("-b", "--base"):
12 cmd = 'base'
13 arg = a
14 elif o in ("-p", "--path"):
15 cmd = 'path'
16 arg = a
17 elif o in ("-s", "--soname"):
18 cmd = 'soname'
19 arg = a
20 elif o in ("-r", "--realname"):
21 cmd = 'realname'
22 arg = a
23 else:
24 assert False, "unhundled option"
25 usage()
26
27 return (cmd, arg)
5行目が引数として指定されたオプションと検索の際のキーワードになるファイル名解析する部分で、
10行目からは、
データベースを検索する部分はリスト4のようにしてみました。このquery()という関数は、
1 import sqlite3
2
3 def query(db, cmd, arg):
4 conn = sqlite3.connect(db)
5 cur = conn.cursor()
6 sql = 'SELECT {0} FROM depends WHERE {0} LIKE "%{1}%" GROUP BY {0};'.format(cmd, arg)
7 cur.execute(sql)
8 tgt = []
9 for i in cur:
10 tgt.append(i[0])
11
12 for i in tgt:
13 if cmd == 'base' or cmd == 'path' :
14 print("{0} needs these libraries".format(i))
15 else:
16 print("{0} used by these binaries".format(i))
17
18 sql = 'SELECT * FROM depends WHERE {0}="{1}";'.format(cmd, i)
19 cur.execute(sql)
20 for row in cur:
21 (base, path, soname, realname) = row
22 if cmd == 'base' or cmd == 'path' :
23 print(" {0}({1})".format(soname, realname))
24 else:
25 print(" {0}({1})".format(base, path))
26 print
この関数では6行目と18行目の2箇所でSQL文を生成しています。6行目のSQL文では、
たとえば、
SELECT soname FROM depends WHERE soname LIKE "%libcups%" GROUP BY soname;
といったSQL文に展開されるわけです。
sqlite3コマンドを使って実際にこのSQL文で検索してみると、
$ sqlite3 depends.sql3 SQLite version 3.7.10 2012-01-16 13:28:40 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> SELECT soname FROM depends WHERE soname LIKE "%libcups%" GROUP BY soname; libcups.so.2 libcupscgi.so.1 libcupsdriver.so.1 libcupsimage.so.2 libcupsmime.so.1 libcupsppdc.so.1 sqlite>
この結果を、
SELECT * FROM depends WHERE soname="libcups.so.2";
このSQL文で再度データベースを検索すれば、
sqlite> SELECT * FROM depends WHERE soname="libcups.so.2"; epdfview|/usr/bin/epdfview|libcups.so.2|/usr/lib64/libcups.so.2 gs|/usr/bin/gs|libcups.so.2|/usr/lib64/libcups.so.2 gsc|/usr/bin/gsc|libcups.so.2|/usr/lib64/libcups.so.2 gsx|/usr/bin/gsx|libcups.so.2|/usr/lib64/libcups.so.2 ....
この結果を21行目から25行目で整形して表示しているわけです。
実際のスクリプトではこれら以外にも、
スクリプトの活用例
依存関係情報を収集するスクリプトをget_
まず正引きの例として、
$ ./query_depends.py -b cat bdftruncate needs these libraries linux-vdso.so.1(none) libc.so.6(/lib64/libc.so.6) /lib64/ld-linux-x86-64.so.2(/lib64/ld-linux-x86-64.so.2) cat needs these libraries linux-vdso.so.1(none) libc.so.6(/lib64/libc.so.6) /lib64/ld-linux-x86-64.so.2(/lib64/ld-linux-x86-64.so.2) catia.so needs these libraries linux-vdso.so.1(none) libc.so.6(/lib64/libc.so.6) /lib64/ld-linux-x86-64.so.2(/lib64/ld-linux-x86-64.so.2) ...
ファイル名の一部からだとマッチする件数が多すぎる場合は、
$ ./query_depends.py -p /bin/cat /bin/cat needs these libraries linux-vdso.so.1(none) libc.so.6(/lib64/libc.so.6) /lib64/ld-linux-x86-64.so.2(/lib64/ld-linux-x86-64.so.2)
共有ライブラリ名の一部から逆引きするには-s オプションを指定します。
$ ./query_depends.py -s poppler libpoppler-glib.so.5 used by these binaries inkscape(/usr/bin/inkscape) inkview(/usr/bin/inkview) poppler.so(/usr/lib64/python2.7/site-packages/poppler.so) libpdfdocument.so(/usr/lib64/evince/3/backends/libpdfdocument.so) tumbler-poppler-thumbnailer.so(/usr/lib64/tumbler-1/plugins/tumbler-poppler-thumbnailer.so) libpoppler-glib.so.8 used by these binaries epdfview(/usr/bin/epdfview) poppler-glib-demo(/usr/bin/poppler-glib-demo) file-pdf(/usr/lib64/gimp/2.0/plug-ins/file-pdf) ...
これは当初考えていなかった機能ですが、
$ ./query_depends.py -r found not found used by these binaries m_xt.so(/lib/tc/m_xt.so) libwx_gtk2u_html-2.8.so.0.8.0(/usr/lib64/libwx_gtk2u_html-2.8.so.0.8.0) libwx_gtk2u_xrc-2.8.so.0.8.0(/usr/lib64/libwx_gtk2u_xrc-2.8.so.0.8.0) libwx_gtk2u_richtext-2.8.so.0.8.0(/usr/lib64/libwx_gtk2u_richtext-2.8.so.0.8.0) libboost_graph.so.1.45.0(/usr/lib64/libboost_graph.so.1.45.0) libboost_graph.so.1.45.0(/usr/lib64/libboost_graph.so.1.45.0) libboost_graph.so.1.45.0(/usr/lib64/libboost_graph.so.1.45.0) libboost_graph.so.1.45.0(/usr/lib64/libboost_graph.so.1.45.0) ....
どんな共有ライブラリが見つからなかったかを調べるには、
$ ./query_depends.py -b libboost_graph.so.1.45.0 libboost_graph.so.1.45.0 needs these libraries linux-vdso.so.1(none) libboost_regex.so.1.45.0(/usr/lib64/libboost_regex.so.1.45.0) libicuuc.so.46(not found) libicui18n.so.46(not found) ...
あれれ、
./query_depends.py -b libicuuc.so libicuuc.so.44.2 needs these libraries linux-vdso.so.1(none) libicudata.so.44(/opt/libreoffice3.4/basis3.4/program/libicudata.so.44) libpthread.so.0(/lib64/libpthread.so.0) ... libicuuc.so.48.1.1 needs these libraries linux-vdso.so.1(none) libicudata.so.48(/usr/lib64/libicudata.so.48) libpthread.so.0(/lib64/libpthread.so.0) ...
libicuuc.
$ grep libicuuc /var/log/packages/* /var/log/packages/icu:usr/lib64/libicuuc.so.48.1.1 /var/log/packages/libobasis3.4_core05:opt/libreoffice3.4/basis3.4/program/libicuuc.so.44.2
このうち、
参照が解決できない共有ライブラリ
$ ./query_depends.py -s libboost_graph $
何のことはない、
さて、
本連載では3回に渡ってSQLiteを用いた依存関係管理システムを紹介してみました。
実のところ、
今回紹介したスクリプトを使えば、
テキストベースでは管理しづらい規模だけど、