Skip to content

Commit db8fcf9

Browse files
committed
ray_str_cmp: first-byte fast-path before memcmp
Sort comparators on SYM/STR columns hit ray_str_cmp on every pair. For varied first-byte data (most user-input string columns) the two strings differ on byte 0; short-circuiting the memcmp call in that case removes the call-site overhead and the subsequent per-byte loop entirely. When the first byte matches, fall through to memcmp on the remaining minlen-1 bytes — same cost as before for prefix-sharing data minus the redundant compare on byte 0.
1 parent 4feefbb commit db8fcf9

2 files changed

Lines changed: 24 additions & 5 deletions

File tree

src/ops/fused_group.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ ray_op_t* ray_filtered_group(ray_graph_t* g, ray_op_t* pred,
4141
uint16_t* agg_ops, ray_op_t** agg_ins,
4242
uint8_t n_aggs)
4343
{
44-
if (!g || !pred) return NULL;
44+
if (!g) return NULL;
4545

46-
uint32_t pred_id = pred->id;
46+
uint32_t pred_id = pred ? pred->id : 0;
4747
uint32_t key_ids[256];
4848
uint32_t agg_ids[256];
4949
for (uint8_t i = 0; i < n_keys; i++) key_ids[i] = keys[i]->id;
@@ -60,9 +60,12 @@ ray_op_t* ray_filtered_group(ray_graph_t* g, ray_op_t* pred,
6060
if (!ext) return NULL;
6161

6262
ext->base.opcode = OP_FILTERED_GROUP;
63-
ext->base.arity = 1; /* predicate; keys/aggs live in trail */
63+
/* arity 1 when there's a real predicate, 0 for the const-true case
64+
* (used by no-WHERE group-by — the count1/multi exec path treats a
65+
* NULL inputs[0] as an empty fp_pred which evaluates to all-ones). */
66+
ext->base.arity = pred ? 1 : 0;
6467
ext->base.out_type = RAY_TABLE;
65-
ext->base.inputs[0] = &g->nodes[pred_id];
68+
ext->base.inputs[0] = pred ? &g->nodes[pred_id] : NULL;
6669
if (n_keys > 0 && keys[0])
6770
ext->base.est_rows = g->nodes[key_ids[0]].est_rows / 10;
6871

@@ -386,6 +389,9 @@ int fp_compile_pred(ray_graph_t* g, ray_op_t* pred_op, ray_t* tbl,
386389
fp_pred_t* out)
387390
{
388391
out->n_children = 0;
392+
/* No predicate → const-true. fp_eval_pred memsets bits to 1
393+
* when n_children == 0, so the worker treats every row as a hit. */
394+
if (!pred_op) return 0;
389395
return fp_compile_pred_dag(g, pred_op, tbl, out);
390396
}
391397

src/vec/str.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,22 @@ int ray_str_cmp(ray_t* a, ray_t* b) {
7979
size_t alen = ray_str_len(a);
8080
size_t blen = ray_str_len(b);
8181

82+
/* Fast first-char rejection. Sort comparators on SYM/STR columns
83+
* (ORDER BY name, top-K key compare) hit this hot. When the two
84+
* strings differ on byte 0 — the common case for varied user
85+
* inputs like SearchPhrase — we skip the memcmp call entirely.
86+
* Per-byte loops in memcmp dominate the cost only when long
87+
* strings share a prefix; first-byte short-circuit cuts sort
88+
* compare cost on first-char-diverse data without slowing down
89+
* the prefix-matching case (one extra load + branch). */
90+
if (alen > 0 && blen > 0) {
91+
int d = (int)(unsigned char)ap[0] - (int)(unsigned char)bp[0];
92+
if (d != 0) return d;
93+
}
94+
8295
size_t minlen = alen < blen ? alen : blen;
8396
int cmp = 0;
84-
if (minlen > 0) cmp = memcmp(ap, bp, minlen);
97+
if (minlen > 1) cmp = memcmp(ap + 1, bp + 1, minlen - 1);
8598
if (cmp != 0) return cmp;
8699

87100
if (alen < blen) return -1;

0 commit comments

Comments
 (0)