hash
Table of Contents
BDB 1.85 と 5.3 の比較
Berkeley DB で、バージョンが変わると速度にどの様な変化が出るんだろう? FreeBSD 13.1(i386)で、確認してみる。5.3版は自前でコンパイルしたんで、 下記のようにして準備した。スレッドが有効になってるんですねぇ。
cc big5.c -I/home/sakae/MINE/include -L/home/sakae/MINE/lib -ldb -dl -lthr
結果はgnuplotでグラフにした。
set terminal svg set output "btree.svg" set title "BDB5.3" plot "btree.log" using 1:2 w l, "btree.log" using 1:4 w l
100万個のキー/データを登録。それぞれ1万個単位で、登録に要した時間(秒)をY軸 にした。
5.3の方が遅いな。きっと機能が増えた分、余計な事をやっているのでしょう。 それよりハッシュの波うちが気になる。何でかな? (hxx.logはハッシュDB, bxx.logは B木によるDB)
hash expand
少し深く潜ってみるか。
#0 __addel (hashp=0x6c175800, bufp=0x6c14f160, key=0xcf7d07b4, val=0xcf7d07ac) at /usr/src/lib/libc/db/hash/hash_page.c:441 #1 0x0c91760a in hash_access (hashp=<optimized out>, action=<optimized out>, key=0xcf7d07b4, val=0xcf7d07ac) at /usr/src/lib/libc/db/hash/hash.c:636 #2 0x0c916bb6 in hash_put (dbp=0x6c16f940, key=0xcf7d07b4, data=0xcf7d07ac, flag=0) at /usr/src/lib/libc/db/hash/hash.c:532 #3 0x1a49c8c2 in reg_kv (mykey=0x1a49b3b0 "HACKING") at mydb.c:19 #4 0x1a49c98a in main () at mydb.c:26
キー、データ登録ルーチンの最後の所。
/* * If the average number of keys per bucket exceeds the fill factor, * expand the table. */ hashp->NKEYS++; if (do_expand || (hashp->NKEYS / (hashp->MAX_BUCKET + 1) > hashp->FFACTOR)) return (__expand_table(hashp)); return (0);
テーブル拡張時の状態。テーブルに占めるキーの数が増加すると、テーブルが 拡張される。その為に大量のデータ移動が裏で実行されるんだな。
(gdb) p (*hashp)->hdr max_bucket = 1, ffactor = 399, nkeys = 756, ;; keyの登録数 : max_bucket = 37, ;; expand_tableが呼ばれる度に +1 される。 ffactor = 399, nkeys = 13222,
hash
ふと、rubyやschemeでsymbolの扱いがどうなってるか気になった。
irb(main):001:0> a = {} irb(main):003:0> a["hoge"] = 123 => 123 irb(main):005:0> a[:foo] = 2 => 2 irb(main):006:0> a => {"hoge"=>123, :foo=>2}
gosh$ (define a (make-hash-table)) gosh$ (hash-table-put! a "hoge" 123) gosh$ (hash-table-put! a 'foo 2) gosh$ (hash-table-map a (lambda (k v) (vector k v))) (#(foo 2) #("hoge" 123))
pythonはどうかなとググッてみたら、 symbol — Python 解析木と共に使われる定数 こんなのが申し訳程度に出てきて、
hash of gosh
代表的な例として、やってみる。方式は前回のバークレーDBと同じように、 100万件のキーとデータの登録時間。データは、キーを反転したものにした。
(use srfi-13) ;; string-reverse (use srfi-19) ;; time (define a (make-hash-table)) (define st (current-time)) (define sp (current-time)) (define dif (time-difference sp st)) (with-input-from-file "./SEED" (lambda () (generator-fold (lambda (key cnt) (when (= 0 (mod cnt 10000)) (set! sp (current-time)) (set! dif (time-difference sp st)) (set! st sp) ;;(format #t "~7d ~a\n" cnt dif) (format #t "~7d ~a.~a\n" cnt (time-second dif) (time-nanosecond dif))) (hash-table-put! a key (string-reverse key)) (+ cnt 1)) 1 read-line)))
久ぶりなんでGauche本を参照。それで不足してる情報はinfoのCとSchemeの関数の対応 のセクションを参照して、scheme言語に変換した。
10000 #<time-duration 0.163062000> 20000 #<time-duration 0.317233000>
時間の引き算が定義されてて、こんな風に型と共に表示される(コメントを外 すと確認できる)。秒とナノ秒でも取り出せるんで、それを合成した。
10000 0.200341000 : 990000 3.415444000 1000000 2.359766000
結果は、上記のようになった。普通の世界だな。
gosh> (gc-stat) ((:total-heap-size 3346432) (:free-bytes 32768) (:bytes-since-gc 995440) (:total-bytes 4691248)) : ((:total-heap-size 161304576) (:free-bytes 1929216) (:bytes-since-gc 1160200) (:total-bytes 340380240))
普通じゃないのは、裏でゴミ集めが行なわれる事。開始前と終了後の状態を確 認してみた。
tree-map of gosh
ハッシュに対抗して、赤黒木によるものも実装されている。まるで、BDBな世 界だ。使い方は簡単で 、make-hash-table/hash-table-put!の部分を make-tree-map/tree-map-put!に変更するだけ。
X軸の表示が渋滞して酷い事になっててスマソ。一目盛が10万。最右が100万と なってますんで、心眼で宜しく。
hash and gc
走っている最中に止めたら、gcしてた。いつもgcしてる。
(gdb) bt 12 #0 0x2072dd0b in GC_push_contents_hdr (current=0x2235efe0 "`a-\"@a-\"\220\260\034\"", <incomplete sequence \326>, mark_stack\ _top=<optimized out>, mark_stack_limit=0x20e28000, source=<optimized out>, hhdr=0x223d0f98, do_offset_check=1) at ./include/p\ rivate/gc_pmark.h:349 #1 0x2072a917 in GC_mark_from (mark_stack_top=0x2235efe0, mark_stack=0x20e20000, mark_stack_limit=0x20e28000) at extra/../ma\ rk.c:888 #2 0x20722e61 in GC_mark_some (cold_gc_frame=0xffbfe020 "P\340\277\377:\033r P\023r ") at extra/../mark.c:394 #3 0x207221fb in GC_stopped_mark (stop_func=0x20721350 <GC_never_stop_func>) at extra/../alloc.c:793 #4 0x20721b3a in GC_try_to_collect_inner (stop_func=0x20721350 <GC_never_stop_func>) at extra/../alloc.c:541 #5 0x20728771 in GC_grow_table (table=0x208eb240 <GC_fnlz_roots>, log_size_ptr=0x2086fdb8 <log_fo_table_size>, entries_ptr=0\ x208eb19c <GC_fo_entries>) at extra/../finalize.c:133 #6 0x2072a009 in GC_register_finalizer_inner (obj=0x224da4c8, fn=0x205cd330 <port_finalize>, cd=0x0, ofn=0xffbfe110, ocd=0xf\ fbfe114, mp=0x20729ea0 <GC_null_finalize_mark_proc>) at extra/../finalize.c:707 #7 0x2072806b in GC_register_finalizer_no_order (obj=0x224da4c8, fn=0x205cd330 <port_finalize>, cd=0x0, ofn=0xffbfe110, ocd=\ 0xffbfe114) at extra/../finalize.c:848 #8 0x205782e9 in Scm_RegisterFinalizer (z=0x224da4c8, finalizer=0x205cd330 <port_finalize>, data=0x0) at core.c:326 #9 0x205c5b3b in make_port (klass=<optimized out>, name=<optimized out>, dir=2, type=2) at port.c:222 #10 0x205cc2b3 in Scm_MakeOutputStringPortFull (name=0x2075ec2c <scm.sc+2408>, flags=0) at port.c:1505 #11 0x2068990c in libioopen_output_string (SCM_FP=0x20e44284, SCM_ARGCNT=1, data_=0x0) at libio.scm:394 (More stack frames follow...)
しょうがないので、ハッシュのテーブルを拡張する所にBPを設置。
(gdb) b hash.c:573 Breakpoint 1 at 0x205c0e9c: file hash.c, line 573. (gdb) c Continuing. Breakpoint 1, insert_entry (table=<optimized out>, key=<optimized out>, hashval\ =1523131600, index=32665) at hash.c:573 573 int newsize = (table->numBuckets << EXTEND_BITS); (gdb) bt #0 insert_entry (table=<optimized out>, key=<optimized out>, hashval=1523131600, index=32665) at hash.c:573 #1 0x205beff8 in address_access (table=0x21000eb8, key=582397568, op=SCM_DICT_CREATE) at hash.c:650 #2 0x205bfe4c in Scm_HashCoreSearch (table=0x30000, op=(unknown: 0x30000), key=<optimized out>) at hash.c:894 #3 Scm_HashTableSet (ht=0x21000eb0, key=0x22b6ae80, value=0x22b6ae60, flags=0) at hash.c:1002 #4 0x2066b320 in libdicthash_table_putX (SCM_FP=0x20e4420c, SCM_ARGCNT=3, data_=0x0) at libdict.scm:283 #5 0x20580bf9 in run_loop () at ././vmcall.c:192 #6 0x2057a08d in user_eval_inner (program=<optimized out>, codevec=0xffbfe87c) at vm.c:1572 #7 0x2057aaf7 in apply_rec (vm=<optimized out>, proc=<optimized out>, nargs=<optimized out>) at vm.c:1691 #8 Scm_ApplyRec (proc=0x20ea98d0, args=<optimized out>) at vm.c:1711 #9 0x20601221 in Scm_Load (cpath=0x20ef3640 "./hash.scm", flags=20, packet=0xffbfe97c) at load.c:231 #10 0x00405a73 in execute_script (scriptfile=0x20ef3640 "./hash.scm", args=0x20fbc290) at main.c:684 #11 0x004064a9 in main (ac=2, av=0xffbfeb24) at main.c:895
こんな回数で、拡張が行わてていた。
190000 0.612608000 : 780000 2.611347000
hash of Chez Scheme
goshばかりではなくて、Chez Schemeも持ってる事を思い出した。 ファイルから種を読み出すのが面倒だったので、shellの命令seqを埋めこんで みた(seq -f q%.6fp 0 0.000997 1000)。また、string-reverseも不足してたんで手抜きしてる。
(define a (make-hash-table)) (define st (current-time)) (define sp (current-time)) (define dif (time-difference sp st)) (let loop ((seq 0.0) (cnt 1)) (when (< seq 1000.0) (when (= 0 (mod cnt 10000)) (set! sp (current-time)) (set! dif (time-difference sp st)) (set! st sp) (format #t "~7d ~a.~a\n" cnt (time-second dif) (time-nanosecond dif))) (hashtable-set! a (format #f "q~ap" seq) cnt) (loop (+ seq 0.000997) (+ cnt 1)))) (exit)
やっぱりガツンとごみ収集が実施されてますなあ。Lisp/Schemeの宿命か。
詳しい事は、Section 7.12. Hashtables
hash of ruby
cnt = 1 a = {} st = Time.now() File.foreach("./SEED") do |line| key = line.chomp() if (cnt % 10000 == 0) then sp = Time.now() printf("%7d %f\n", cnt, sp - st) st = sp end cnt += 1 a[key] = key.reverse() end
結構、山が有るな。まあ、大量のデータですからね。無理もないか。
dict of python
次はpythonでハッシュもとえ辞書のスピード試験。
import time cnt = 1 a = {} st = time.time() with open('./SEED') as f: while(True): line = f.readline().rstrip() if(not line): break if cnt % 10000 == 0 : sp = time.time() print(cnt, sp - st ) st = sp a[line] = line[::-1] # reverse cnt = cnt + 1
ごみ収集は、まとめてドンって言う方式じゃなくて、こまめにやるんで隠され ているんだな。一時的に遅くなるのは、辞書の拡張のせいか。
sympy
pythonでシンボルを検索すると、上にあげたものしかヒットせず。後は、 SymPyの類ばかり。maximaになりたいのか。
toolz
tips of gdb
関数ポインターへのBP
こんなのにBPを設置したい。
(void)(db->put)(db, &key, &data, 0);
中身はこう。
(gdb) p *db $1 = {type = DB_BTREE, close = 0x440f90 <db185_close>, del = 0x441010 <db185_del>, get = 0x4411b0 <db185_get>, put = 0x4412f0 <db185_put>, seq = 0x441690 <db185_seq>, sync = 0x441890 <db185_sync>, internal = 0x2089c000, fd = 0x441150 <db185_fd>} (gdb) b *(db->put) Breakpoint 2 at 0x4412f0: file ../lang/db185/db185.c, line 369. (gdb) c Continuing. Breakpoint 2, db185_put (db185p=0x415400, key185=0x202, data185=0x1e4, flags=1) at ../lang/db185/db185.c:369 369 {
gcore
今迄さかんにgdbのbtの結果を載せてきた。後でそれを道標にしてソースを追 求するんだった。そう、cscopeやらetagと併用してね。でも、いささか面倒。 そんな時、任意の時点でコアファイルを作成する方法を知った。使い方は簡単。
(gdb) gcore Saved corefile core.7244
この機能はOpenBSDではサポートしていない。
[sakae@fb /tmp]$ gdb -q a.out core.7244 Reading symbols from a.out... [New LWP 100159] Core was generated by `a.out'. Program terminated with signal SIGTRAP, Trace/breakpoint trap. #0 db185_put (db185p=0x208aa000, key185=0xffbfea70, data185=0xffbfea68, flags=0) at ../lang/db185/db185.c:375 375 dbp = db185p->dbp; (gdb)
後でじっくり、軌跡を追跡できる。道草も大丈夫だ。迷っても、一度up/down すれば、元の所に復帰できる。