datalog: hash-set membership for facts (Phase 5b perf)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s

db gains a parallel :facts-keys {<rel>: {<tuple-string>: true}}
index alongside :facts. dl-tuple-key derives a stable string via
(str lit) — (p 30) and (p 30.0) collide correctly because SX
prints them identically. dl-add-fact! membership is now O(1)
instead of O(n) list scan; insert sequences for relations sized
N drop from O(N²) to O(N).

Wall clock on chain-7 saturation halves (~12s → ~6s); chain-15
roughly halves (~50s → ~25s) under shared CPU. Larger chains
still slow due to body-join overhead in dl-find-bindings —
Blocker entry refreshed with proposed follow-ups.

dl-retract! keeps both indices consistent: kept-keys is rebuilt
during the EDB filter, IDB wipes clear both lists and key dicts.
This commit is contained in:
2026-05-08 08:42:10 +00:00
parent ce603e9879
commit 3cc760082c
3 changed files with 47 additions and 15 deletions

View File

@@ -111,21 +111,29 @@
(has-key? (get db :facts) rel-key)
(let
((existing (get (get db :facts) rel-key))
(kept (list)))
(kept (list))
(kept-keys {}))
(do
(for-each
(fn
(t)
(when
(not (dl-tuple-equal? t lit))
(append! kept t)))
(do
(append! kept t)
(dict-set! kept-keys (dl-tuple-key t) true))))
existing)
(dict-set! (get db :facts) rel-key kept))))
(dict-set! (get db :facts) rel-key kept)
(dict-set! (get db :facts-keys) rel-key kept-keys))))
;; Wipe all relations that have a rule (these are IDB) so the
;; saturator regenerates them from the surviving EDB.
(let ((rule-heads (dl-rule-head-rels db)))
(for-each
(fn (k) (dict-set! (get db :facts) k (list)))
(fn
(k)
(do
(dict-set! (get db :facts) k (list))
(dict-set! (get db :facts-keys) k {})))
rule-heads))
(dl-saturate! db)
db))))