// Per-candidate predicate specialization: during one bind-chain level's// candidate iteration every row slot except the level's own is fixed, so// a pushed-down conjunct is a unary function of the candidate id. The// dominant conjunct shape -- the fused prop-vs-const comparison -- runs// as a monomorphic closure reading its hoisted typed column directly,// skipping the compiled tree's dispatch and per-candidate boxing. Every// pairing whose comparison semantics are not trivially same-kind boxes// the column value once and routes through value.Compare, so results are// exactly the general path's (missing property = failed compare = Null =// prune, for all six operators).
package compileimport ()// CandPred reports whether the conjunct holds for a node candidate bound// at its specialized slot. The row carries the level's fixed outer// bindings -- only the carried-list rebuild (once per match-call epoch)// reads it; hot paths touch the candidate alone.typeCandPredfunc(ctx *eval.Ctx, row []value.Value, id graph.NodeID) bool// CandidatePred specializes c when its only per-candidate dependency is// the node at slot and its shape is the fused prop-vs-const comparison or// a constant- or carried-list membership over the candidate's property// (or over the candidate itself: a bare-variable element probes the node// value directly, no read at all); ok=false keeps the general row// evaluation.func ( *Compiled, int, map[string]int) (CandPred, bool) {// inElem resolves a membership element over slot: a property read, or // the identity (the candidate node itself); nil declines. := func( cnode) func( graph.NodeID) value.Value {switch n := .(type) {case *cProp:if .slot != {returnnil } := .readerreturn .readNodecase *cSlot:if .s != {returnnil }returnfunc( graph.NodeID) value.Value { returnvalue.Node() } }returnnil }// bareSlot reports whether the membership element IS the candidate // itself -- then a node-id membership probes the sorted id set with // the raw candidate: no boxing, no resultFor dispatch. (memNodes is // built from an all-node list, so hasNull is impossible; a miss // prunes either way under the predicate's truthy collapse.) := func( cnode) bool { , := .(*cSlot)return && .s == }if , := .c.(*cInConst); { := (.e)if == nil {returnnil, false }if (.e) && .m.kind == memNodes { := &.mreturnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool {return .hasNode(uint32()) }, true }// A string column probing a constant list compares interned atom // ids: the list's atoms resolve once, a probe is one column read // and a small search -- no string resolution, no encoded hash key.if , := .e.(*cProp); && .reader.node.kind == colStr {if , := .m.atomSet(.g); { := .reader.node.strreturnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32())if ! || == 0 {returnfalse } , := slices.BinarySearch(, )return }, true } } := &.mreturnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { := ()if .IsNull() {returnfalse }return .resultFor().IsTruthy() }, true }if , := .c.(*cInCarried); { := (.e)if == nil {returnnil, false } := (.e) := .greturnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { .refresh(, , , )if .notList {returnfalse }// The carried list may change per epoch; re-check the // representation per call (one byte compare).if && .m.kind == memNodes {return .m.hasNode(uint32()) } := ()if .IsNull() {returnfalse }return .m.resultFor().IsTruthy() }, true }// IS [NOT] NULL over the candidate's property is a pure presence // probe: one typed column read, no value materialization (the general // path resolves a string column's text just to discard it). The // string form mirrors the reader's empty-means-absent fold by atom // id, declining when a non-zero atom resolves empty (the one case // where the id test could diverge).if , := .c.(*cIsNull); { , := .e.(*cProp)if ! || .slot != {returnnil, false } := .reader.node := .negatedswitch .kind {casecolI64: := .i64returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .Get(uint32())return == }, truecasecolF64: := .f64returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .Get(uint32())return == }, truecasecolBool: := .boolreturnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .Get(uint32())return == }, truecasecolStr:if , := .g.PropertyKey(""); && != 0 {returnnil, false } := .strreturnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32())return ( && != 0) == }, true }returnnil, false } , := .c.(*cCmpPropConst)if ! || .prop.slot != {returnnil, false } := .prop.reader := opKeep(.op) := .rev := .c// Same-kind numeric pairs compare unboxed -- through float64 exactly // like value.Compare's asNum path (Int-vs-Int also routes through // float64 there; mirroring is the invariant, not improving on it). // Everything else boxes the column read once and uses the shared // kernel.if .node.kind == colI64 && .Kind() == value.KindInt { , := .AsInt() := float64()returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .node.i64.Get(uint32())if ! {returnfalse } , := float64(), if { , = , float64() } , := cmpFloat(, )return && () }, true }// An i64 column against a temporal constant compares exactly as // int64 epoch millis (value.Compare's Temporal/Int case is cmpInt -- // exact, always comparable), so the typed closure needs no float // detour.if .node.kind == colI64 && .Kind() == value.KindTemporal { , , := .AsTemporal()returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .node.i64.Get(uint32())if ! {returnfalse } , := , if { , = , }varintswitch {case < : = -1case > : = 1 }return () }, true }// String equality against a constant compares interned atom ids: the // constant resolves through the shared atom table once, so a probe is // one column read and an integer compare -- no string resolution, no // byte comparison. Only =/<> qualify (atom ids carry no lexicographic // order), and both are operand-order symmetric, so rev is moot. The // general path folds absent, atom 0, and empty text to Null (prune); // equality against a non-empty constant needs no empty check (a // matching atom IS the constant's non-empty text), while inequality // declines when a non-zero atom resolves empty (only then could the // id test diverge from the reader's empty-means-absent fold).if .node.kind == colStr && .Kind() == value.KindStr && (.op == ast.OpEq || .op == ast.OpNeq) { , := .AsStr() := .node.str , := .g.PropertyKey()if .op == ast.OpEq {if == "" || ! {// No stored non-empty text can equal it: always prune.returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool {returnfalse }, true }returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32())return && == }, true }if , := .g.PropertyKey(""); && != 0 {// A non-zero empty atom exists: keep the general path, whose // resolve-and-check folds it to absent.returnnil, false }if == "" || ! {// Present non-empty text is unequal to it by construction.returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32())return && != 0 }, true }returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .ID(uint32())return && != 0 && != }, true }if .node.kind == colF64 && .Kind() == value.KindFloat { , := .AsFloat()returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .node.f64.Get(uint32())if ! {returnfalse } , := , if { , = , } , := cmpFloat(, )return && () }, true }returnfunc( *eval.Ctx, []value.Value, graph.NodeID) bool { , := .readNode(), if { , = , } , := value.Compare(, )return && () }, true}// CandBatch sweeps a whole candidate buffer for one conjunct: it clears// keep[i] for every candidate that fails, touching only still-kept// entries. The batch form exists for shapes whose per-candidate work is// pure array arithmetic -- no closure call, no dispatch per element.typeCandBatchfunc(ctx *eval.Ctx, row []value.Value, cand []graph.NodeID, keep []bool)// CandidateBatch specializes c to a buffer sweep when its shape allows// the columnar form: today the fused prop-vs-const comparison over an// i64 column with a contiguous-presence window (SliceRange), where the// filter reduces to vals[id-start] compared against a pre-encoded// constant -- the dominant pushed-down filter shape over LDBC-style// label-block columns. Semantics mirror the scalar predicate exactly// (value.Compare's float64 partial order; out-of-window = absent =// prune). ok=false keeps the scalar path.func ( *Compiled, int) (CandBatch, bool) { , := .c.(*cCmpPropConst)if ! || .prop.slot != {returnnil, false } := .prop.reader.nodeif .kind != colI64 || .c.Kind() != value.KindInt {returnnil, false } , , := .i64.SliceRange()if ! {returnnil, false } , := .c.AsInt() := float64() := opKeep(.op) := .revreturnfunc( *eval.Ctx, []value.Value, []graph.NodeID, []bool) {for , := range {if ![] {continue } := uint32() - if >= uint32(len()) { [] = falsecontinue } , := float64([]), if { , = , float64([]) } , := cmpFloat(, )if ! || !() { [] = false } } }, true}// opKeep is the six comparison operators' order acceptance.func opKeep( ast.BinOp) func(int) bool {switch {caseast.OpEq:returnfunc( int) bool { return == 0 }caseast.OpNeq:returnfunc( int) bool { return != 0 }caseast.OpLt:returnfunc( int) bool { return < 0 }caseast.OpLte:returnfunc( int) bool { return <= 0 }caseast.OpGt:returnfunc( int) bool { return > 0 }default: // ast.OpGtereturnfunc( int) bool { return >= 0 } }}// cmpFloat is value.Compare's float64 partial order: NaN on either side// is incomparable.func cmpFloat(, float64) (int, bool) {switch {case < :return -1, truecase > :return1, truecase == :return0, true }return0, false}// IntSweep bulk-filters a candidate chunk over an i64 NODE column// against a folded constant: one GetMany then a hoisted compare loop --// the batch form of the cCmpPropConst per-candidate predicate for// columns the windowed CandidateBatch cannot serve (narrow storage// classes) and constants it declines (temporals, which fold to their// epoch millis and compare through the exact int order, per constI64Val).// vals/present are caller scratch of at least len(ids); keep is the// running mask. Candidates must be node ids, the same contract as// CandidateBatch.typeIntSweepfunc(ids []uint32, vals []int64, present, keep []bool)// CandidateIntSweep specializes a compiled conjunct to an IntSweep;// ok=false leaves the caller on the per-candidate path.func ( *Compiled, int) (IntSweep, bool) { , := .c.(*cCmpPropConst)if ! || .prop.slot != {returnnil, false } := .prop.reader.nodeif .kind != colI64 {returnnil, false } , , := constI64Val(.c)if ! {returnnil, false } := .i64// A reversed operand order mirrors the operator, so the loops below // always compare value-vs-constant. := .opif .rev {switch {caseast.OpLt: = ast.OpGtcaseast.OpLte: = ast.OpGtecaseast.OpGt: = ast.OpLtcaseast.OpGte: = ast.OpLte } }if {// The exact int order inlines per operator: the per-id closure // call and compare-function call were the sweep loop's dominant // cost at 5.7M candidates/query.varIntSweepswitch {caseast.OpEq: = func( []uint32, []int64, , []bool) { .GetMany(, , )for := range { [] = [] && [] && [] == } }caseast.OpNeq: = func( []uint32, []int64, , []bool) { .GetMany(, , )for := range { [] = [] && [] && [] != } }caseast.OpLt: = func( []uint32, []int64, , []bool) { .GetMany(, , )for := range { [] = [] && [] && [] < } }caseast.OpLte: = func( []uint32, []int64, , []bool) { .GetMany(, , )for := range { [] = [] && [] && [] <= } }caseast.OpGt: = func( []uint32, []int64, , []bool) { .GetMany(, , )for := range { [] = [] && [] && [] > } }default: // ast.OpGte = func( []uint32, []int64, , []bool) { .GetMany(, , )for := range { [] = [] && [] && [] >= } } }return , true } := opKeep()returnfunc( []uint32, []int64, , []bool) { .GetMany(, , )for := range {if ![] || ![] { [] = falsecontinue } , := cmpFloat(float64([]), float64()) [] = && () } }, true}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.