diff --git a/hbrtl/sqlscan.go b/hbrtl/sqlscan.go index cdee027..2d8e71d 100644 --- a/hbrtl/sqlscan.go +++ b/hbrtl/sqlscan.go @@ -138,20 +138,26 @@ func SqlScan(t *hbrt.Thread) { } defer func() { t.FastFieldGetter = prevFG }() - // Scan — dispatch two nearly-identical loops for devirtualization. - // The DBF branch is the common case; Go's compiler inlines the - // direct method calls, whereas the generic Area branch pays one - // interface dispatch per call as before. - if dbfArea != nil { + // Scan — four specialized loops. Two axes of specialization: + // + // DBF vs generic Area: devirtualization — Go inlines method calls + // on the concrete type but pays an interface + // dispatch on every call of the generic one. + // + // WHERE vs no-WHERE : branch hoisting — the no-WHERE case is a + // hot full-scan path (SELECT * or similar), + // where even the predictable `whereFn != nil` + // check and the `keep` shadow variable show + // up in pprof. + // + // Four combinations = four loop copies. Painful but each row save + // counts when we're reaching for raw RDD parity. + switch { + case dbfArea != nil && whereFn != nil: dbfArea.GoTop() for !dbfArea.EOF() { - keep := true - if whereFn != nil { - hbrt.ExecPcodeFast(t, whereFn, nil) - keep = t.GetRetValue().AsBool() - } - - if keep { + hbrt.ExecPcodeFast(t, whereFn, nil) + if t.GetRetValue().AsBool() { off := len(flat) end := off + nFields if end > cap(flat) { @@ -166,19 +172,32 @@ func SqlScan(t *hbrt.Thread) { } rows = append(rows, slab.WrapNext(row)) } - dbfArea.Skip(1) } - } else { + case dbfArea != nil: + // DBF + no WHERE — tightest inner loop + dbfArea.GoTop() + for !dbfArea.EOF() { + off := len(flat) + end := off + nFields + if end > cap(flat) { + flat = append(flat, make([]hbrt.Value, nFields)...) + } else { + flat = flat[:end] + } + row := flat[off:end:end] + for i := 0; i < nFields; i++ { + v, _ := dbfArea.GetValue(fieldPos[i] - 1) + row[i] = v + } + rows = append(rows, slab.WrapNext(row)) + dbfArea.Skip(1) + } + case whereFn != nil: area.GoTop() for !area.EOF() { - keep := true - if whereFn != nil { - hbrt.ExecPcodeFast(t, whereFn, nil) - keep = t.GetRetValue().AsBool() - } - - if keep { + hbrt.ExecPcodeFast(t, whereFn, nil) + if t.GetRetValue().AsBool() { off := len(flat) end := off + nFields if end > cap(flat) { @@ -193,7 +212,24 @@ func SqlScan(t *hbrt.Thread) { } rows = append(rows, slab.WrapNext(row)) } - + area.Skip(1) + } + default: + area.GoTop() + for !area.EOF() { + off := len(flat) + end := off + nFields + if end > cap(flat) { + flat = append(flat, make([]hbrt.Value, nFields)...) + } else { + flat = flat[:end] + } + row := flat[off:end:end] + for i := 0; i < nFields; i++ { + v, _ := area.GetValue(fieldPos[i] - 1) + row[i] = v + } + rows = append(rows, slab.WrapNext(row)) area.Skip(1) } }