#include "ops/agg_engine.h"
#include "ops/agg_registry.h"
#include "ops/ops.h"
#include "ops/hash.h"
#include "ops/internal.h"
#include "ops/rowsel.h"
#include "lang/internal.h"
#include "table/domain.h"
#include "table/sym.h"
#include "core/platform.h"
#include <stdlib.h>
#include <math.h>
#include <string.h>
typedef struct { int64_t idx; } agg_radix_order_t;
bool ray_agg_engine_v2 = true;
bool ray_agg_tablestack = true;
static _Thread_local agg_route_stats_t route_stats;
void agg_route_reset(void) { memset(&route_stats, 0, sizeof(route_stats)); }
agg_route_stats_t agg_route_stats(void) { return route_stats; }
void agg_route_note_key_domain(void) { route_stats.key_domain_evals++; }
void agg_route_record(agg_route_t route) {
static const char* const names[AGG_ROUTE_COUNT] = {
"group: none", "group: legacy", "group: slices", "group: parted",
"group: v2 serial dense", "group: v2 serial hash", "group: v2 dense",
"group: v2 radix", "group: v2 hash", "group: v2 smallhash", "group: v2 indexed",
"group: v2 tablestack"
};
route_stats.routes[route]++;
ray_profile_tick(names[route]);
}
void agg_route_reason(agg_v2_reason_t reason) {
route_stats.last_v2_reason = reason;
static const char* const names[] = {
"group: v2 admitted", "group: v2 unsupported shape",
"group: v2 key expression", "group: v2 unsupported key type",
"group: v2 aggregate expression", "group: v2 unsupported aggregate type",
"group: v2 buffered aggregate", "group: v2 unsupported parameter",
"group: v2 disabled", "group: v2 emit filter", "group: parallel wide-key strategy"
};
if (reason != AGG_V2_ADMITTED) ray_profile_tick(names[reason]);
}
static inline int64_t agg_read_key_i64(ray_t* col, const void* data, int64_t row);
static int agg_group_keys_dense(ray_t** key_cols, int64_t nrows,
const dense_plan_t* dp, agg_groups_t* out);
static int agg_group_keys_parallel(ray_t** keys, uint32_t nkeys, int64_t rows,
const dense_plan_t* dp, agg_groups_t* out);
static bool agg_cancelled(void) {
ray_pool_t* pool = ray_pool_get();
return pool && atomic_load_explicit(&pool->cancelled, memory_order_relaxed);
}
static bool agg_indexed_supported(uint16_t op, int8_t t) {
bool number = t >= RAY_BOOL && t <= RAY_TIMESTAMP;
bool scalar = number || t == RAY_GUID || t == RAY_SYM || t == RAY_STR;
if (op == OP_FIRST || op == OP_LAST) return scalar || t == RAY_LIST;
if (op == OP_MIN || op == OP_MAX) return t == RAY_GUID || t == RAY_STR || t == RAY_SYM;
if (op == OP_MEDIAN || op == OP_QUANTILE) return number;
if (op == OP_MODE || op == OP_TOP_N || op == OP_BOT_N) return scalar;
return false;
}
agg_v2_reason_t agg_v2_admission(ray_graph_t* g, ray_op_t* op, ray_t* tbl) {
if (!g || !op || !tbl) return AGG_V2_SHAPE;
ray_op_ext_t* ext = find_ext(g, op->id);
if (!ext) return AGG_V2_SHAPE;
if (ext->n_keys < 1) return AGG_V2_SHAPE;
if (ext->n_keys == 1 && op_node(g, ext->keys[0]) && op_node(g, ext->keys[0])->opcode == OP_SCAN) {
ray_op_ext_t* ke = find_ext(g, ext->keys[0]);
if (ke && ke->sym == ray_sym_intern("_src", 4)) {
ray_t* cnt = ray_table_get_col(tbl, ray_sym_intern("_count", 6));
if (cnt && cnt->type == RAY_I64) return AGG_V2_SHAPE;
}
}
for (uint32_t k = 0; k < ext->n_keys; k++) {
ray_op_t* key = op_node(g, ext->keys[k]);
if (!key || key->opcode != OP_SCAN) return AGG_V2_KEY_EXPRESSION;
ray_op_ext_t* kext = find_ext(g, key->id);
ray_t* kc = kext ? ray_table_get_col(tbl, kext->sym) : NULL;
if (!kc) return AGG_V2_SHAPE;
switch (kc->type) {
case RAY_I64: case RAY_I32: case RAY_I16: case RAY_U8:
case RAY_BOOL: case RAY_DATE: case RAY_TIME:
case RAY_TIMESTAMP: case RAY_SYM:
case RAY_F32: case RAY_F64: case RAY_GUID: case RAY_STR: case RAY_LIST: break;
default: return AGG_V2_KEY_TYPE;
}
}
ray_pool_t* pool = ray_pool_get();
uint32_t workers = pool ? ray_pool_total_workers(pool) : 1;
if (ext->n_keys <= 8 && workers >= 2 && workers <= 8 &&
ray_table_nrows(tbl) >= RAY_PARALLEL_THRESHOLD) {
bool floating = false, indexed = false;
for (uint32_t k = 0; k < ext->n_keys; k++) {
ray_op_ext_t* ke = find_ext(g, ext->keys[k]);
ray_t* col = ray_table_get_col(tbl, ke->sym);
if (col->type == RAY_F32 || col->type == RAY_F64) floating = true;
if (col->type == RAY_STR || col->type == RAY_GUID || col->type == RAY_LIST) indexed = true;
}
for (uint32_t a = 0; a < ext->n_aggs; a++) {
ray_op_ext_t* ie = find_ext(g, ext->agg_ins[a]);
ray_t* col = ie ? ray_table_get_col(tbl, ie->sym) : NULL;
if (col && agg_indexed_supported(ext->agg_ops[a], col->type)) indexed = true;
}
if (floating && !indexed) return AGG_V2_PARALLEL_WIDE;
}
for (uint32_t a = 0; a < ext->n_aggs; a++) {
if (ext->agg_k && ext->agg_k[a]) {
if (ext->agg_ops[a] != OP_TOP_N && ext->agg_ops[a] != OP_BOT_N && ext->agg_ops[a] != OP_QUANTILE) return AGG_V2_PARAMETER;
if (ext->agg_ops[a] != OP_QUANTILE && ext->agg_k[a] < 1) return AGG_V2_PARAMETER;
ray_op_t* in = op_node(g, ext->agg_ins[a]);
if (!in || in->opcode != OP_SCAN) return AGG_V2_AGG_EXPRESSION;
ray_op_ext_t* ie = find_ext(g, in->id);
ray_t* ic = ie ? ray_table_get_col(tbl, ie->sym) : NULL;
if (ic && agg_indexed_supported(ext->agg_ops[a], ic->type)) continue;
const agg_vtable_t* vt = ic ? agg_resolve(ext->agg_ops[a], ic->type) : NULL;
if (!vt) return AGG_V2_AGG_TYPE;
if (vt->kind != ACC_STREAMING) return AGG_V2_BUFFERED;
continue;
}
if (ext->agg_ins2 && ext->agg_ins2[a] != RAY_OP_NONE) {
if (!agg_is_binary_agg(ext->agg_ops[a])) return AGG_V2_SHAPE;
ray_op_t* xin = op_node(g, ext->agg_ins[a]); ray_op_t* yin = op_node(g, ext->agg_ins2[a]);
if (!xin || xin->opcode != OP_SCAN || !yin || yin->opcode != OP_SCAN) return AGG_V2_AGG_EXPRESSION;
ray_op_ext_t* xe = find_ext(g, xin->id); ray_op_ext_t* ye = find_ext(g, yin->id);
ray_t* xc = xe ? ray_table_get_col(tbl, xe->sym) : NULL;
ray_t* yc = ye ? ray_table_get_col(tbl, ye->sym) : NULL;
if (!xc || !yc) return AGG_V2_SHAPE;
if (!agg_resolve(ext->agg_ops[a], xc->type)) return AGG_V2_AGG_TYPE;
if (!agg_resolve(ext->agg_ops[a], yc->type)) return AGG_V2_AGG_TYPE;
continue;
}
if (ext->agg_ops[a] == OP_COUNT) {
if (!agg_resolve(OP_COUNT, RAY_I64)) return AGG_V2_SHAPE;
continue;
}
ray_op_t* in = op_node(g, ext->agg_ins[a]);
if (!in || in->opcode != OP_SCAN) return AGG_V2_AGG_EXPRESSION;
ray_op_ext_t* ie = find_ext(g, in->id);
ray_t* ic = (ie) ? ray_table_get_col(tbl, ie->sym) : NULL;
if (!ic) return AGG_V2_SHAPE;
if (agg_indexed_supported(ext->agg_ops[a], ic->type)) continue;
const agg_vtable_t* vt = agg_resolve(ext->agg_ops[a], ic->type);
if (!vt) return AGG_V2_AGG_TYPE;
if (vt->kind != ACC_STREAMING) return AGG_V2_BUFFERED;
if (ext->agg_ops[a] == OP_AVG && ic->type != RAY_F64 && ic->type != RAY_F32 &&
ray_table_nrows(tbl) >= ((int64_t)1 << ((ic->type == RAY_I64 || ic->type == RAY_TIMESTAMP) ? 32 : 31)))
return AGG_V2_AGG_TYPE;
}
return AGG_V2_ADMITTED;
}
bool agg_v2_can_handle(ray_graph_t* g, ray_op_t* op, ray_t* tbl) {
return agg_v2_admission(g, op, tbl) == AGG_V2_ADMITTED;
}
static int64_t agg_key_null(int8_t type) {
switch (type) {
case RAY_I16: return NULL_I16;
case RAY_I32: case RAY_DATE: case RAY_TIME: return NULL_I32;
default: return NULL_I64;
}
}
static inline int64_t agg_dense_component_raw(const dense_plan_t* dp, uint32_t k, int64_t v) {
return dp->nullable[k] && v == dp->nulls[k]
? dp->ranges[k] - 1 : v - dp->mins[k];
}
static inline int64_t agg_dense_component(const dense_plan_t* dp, uint32_t k, int64_t v) {
if (dp->nullable[k] && v == dp->nulls[k]) return dp->ranges[k] - 1;
int64_t c = v - dp->mins[k];
return dp->remap[k] ? dp->remap[k][c] : c;
}
static inline int64_t agg_dense_code(const dense_plan_t* dp, uint32_t k, int64_t component) {
if (dp->nullable[k] && component == dp->ranges[k] - 1) return dp->nulls[k];
return dp->inverse[k] ? dp->inverse[k][component] : dp->mins[k] + component;
}
void agg_dense_plan_free(dense_plan_t* dp) {
if (!dp) return;
for (uint32_t k = 0; k < 16; k++) {
ray_free_raw(dp->remap[k]); dp->remap[k] = NULL;
ray_free_raw(dp->inverse[k]); dp->inverse[k] = NULL;
}
dp->compacted = false;
}
static bool agg_dense_range(dense_plan_t* dp, uint32_t k, int64_t mn, int64_t mx) {
if (mx < mn) { dp->mins[k] = 0; dp->ranges[k] = 1; return dp->nullable[k]; }
uint64_t span = (uint64_t)mx - (uint64_t)mn;
if (span >= (uint64_t)INT64_MAX - (uint64_t)dp->nullable[k]) return false;
dp->mins[k] = mn;
dp->ranges[k] = (int64_t)span + 1 + dp->nullable[k];
return true;
}
static void agg_key_bounds(ray_t* key, int64_t start, int64_t end, bool nullable,
int64_t null, int64_t* lo, int64_t* hi) {
int64_t mn = INT64_MAX, mx = INT64_MIN;
const void* data = ray_data(key);
#define KEY_BOUNDS(T) do { \
const T* p = data; \
if (nullable) { \
for (int64_t r = start; r < end; r++) { \
int64_t v = (int64_t)p[r]; \
int64_t low = v == null ? INT64_MAX : v; \
int64_t high = v == null ? INT64_MIN : v; \
if (low < mn) mn = low; \
if (high > mx) mx = high; \
} \
} else { \
for (int64_t r = start; r < end; r++) { \
int64_t v = (int64_t)p[r]; \
if (v < mn) mn = v; \
if (v > mx) mx = v; \
} \
} \
} while (0)
switch (key->type) {
case RAY_I64: case RAY_TIMESTAMP: KEY_BOUNDS(int64_t); break;
case RAY_I32: case RAY_DATE: case RAY_TIME: KEY_BOUNDS(int32_t); break;
case RAY_I16: KEY_BOUNDS(int16_t); break;
case RAY_U8: case RAY_BOOL: KEY_BOUNDS(uint8_t); break;
case RAY_SYM:
switch (key->attrs & RAY_SYM_W_MASK) {
case RAY_SYM_W8: KEY_BOUNDS(uint8_t); break;
case RAY_SYM_W16: KEY_BOUNDS(uint16_t); break;
case RAY_SYM_W32: KEY_BOUNDS(uint32_t); break;
default: KEY_BOUNDS(int64_t); break;
}
break;
}
#undef KEY_BOUNDS
*lo = mn; *hi = mx;
}
typedef struct {
ray_t* key;
bool nullable;
int64_t null, rows;
uint32_t tasks;
int64_t* bounds;
} agg_key_bounds_ctx_t;
static void agg_key_bounds_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_key_bounds_ctx_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t begin = c->rows / c->tasks * task;
int64_t limit = task + 1 == c->tasks ? c->rows : c->rows / c->tasks * (task + 1);
agg_key_bounds(c->key, begin, limit, c->nullable, c->null,
&c->bounds[task * 2], &c->bounds[task * 2 + 1]);
}
}
static void agg_key_bounds_parallel(ray_t* key, int64_t rows, bool nullable,
int64_t null, int64_t* lo, int64_t* hi) {
ray_pool_t* pool = ray_pool_get();
if (pool && rows >= RAY_PARALLEL_THRESHOLD) {
uint32_t tasks = ray_pool_total_workers(pool);
if (tasks > RAY_POOL_MAX_TASKS) tasks = RAY_POOL_MAX_TASKS;
int64_t* bounds = ray_calloc_raw((size_t)tasks * 2 * sizeof(int64_t));
if (bounds) {
agg_key_bounds_ctx_t c = { key, nullable, null, rows, tasks, bounds };
ray_pool_dispatch_n(pool, agg_key_bounds_fn, &c, tasks);
*lo = INT64_MAX; *hi = INT64_MIN;
for (uint32_t task = 0; task < tasks; task++) {
if (bounds[task * 2] < *lo) *lo = bounds[task * 2];
if (bounds[task * 2 + 1] > *hi) *hi = bounds[task * 2 + 1];
}
ray_free_raw(bounds);
return;
}
}
agg_key_bounds(key, 0, rows, nullable, null, lo, hi);
}
enum { AGG_COMPACT_MAX_RANGE = 1 << 22,
AGG_COMPACT_TRY_SLOTS = 1 << 16 };
#define AGG_COMPACT_BITMAP_BUDGET ((size_t)32 << 20)
typedef struct {
ray_t** key_cols;
const dense_plan_t* dp;
const bool* candidate;
uint32_t n_keys;
int64_t rows;
uint32_t tasks;
uint64_t** task_bits;
} agg_compact_ctx_t;
static void agg_compact_mark_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_compact_ctx_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t begin = c->rows / c->tasks * task;
int64_t limit = task + 1 == c->tasks ? c->rows : c->rows / c->tasks * (task + 1);
for (uint32_t k = 0; k < c->n_keys; k++) {
if (!c->candidate[k]) continue;
uint64_t* bits = c->task_bits[(size_t)task * c->n_keys + k];
ray_t* kc = c->key_cols[k];
const void* d = ray_data(kc);
int64_t mn = c->dp->mins[k];
bool nullable = c->dp->nullable[k];
int64_t null = c->dp->nulls[k];
for (int64_t r = begin; r < limit; r++) {
int64_t v = agg_read_key_i64(kc, d, r);
if (nullable && v == null) continue;
uint64_t code = (uint64_t)(v - mn);
bits[code >> 6] |= UINT64_C(1) << (code & 63);
}
}
}
}
static bool agg_dense_plan_compact(ray_t** key_cols, uint32_t n_keys, int64_t nrows,
int64_t dense_limit, dense_plan_t* out) {
bool candidate[16];
bool any = false;
for (uint32_t k = 0; k < n_keys; k++) {
int64_t raw = out->ranges[k] - out->nullable[k];
candidate[k] = raw > 1 && raw <= AGG_COMPACT_MAX_RANGE;
any |= candidate[k];
}
if (!any) return false;
ray_pool_t* pool = ray_pool_get();
uint32_t tasks = pool && nrows >= RAY_PARALLEL_THRESHOLD ? ray_pool_total_workers(pool) : 1;
if ((int64_t)tasks > nrows) tasks = (uint32_t)nrows;
size_t words_total = 0;
for (uint32_t k = 0; k < n_keys; k++)
if (candidate[k]) words_total += (size_t)((out->ranges[k] - out->nullable[k] + 63) / 64);
while (tasks > 1 && words_total * tasks * sizeof(uint64_t) > AGG_COMPACT_BITMAP_BUDGET) tasks /= 2;
uint64_t* bits = ray_calloc_raw(words_total * tasks * sizeof(uint64_t));
uint64_t** task_bits = ray_calloc_raw((size_t)tasks * n_keys * sizeof(uint64_t*));
if (!bits || !task_bits) { ray_free_raw(bits); ray_free_raw(task_bits); return false; }
{
uint64_t* cursor = bits;
for (uint32_t t = 0; t < tasks; t++)
for (uint32_t k = 0; k < n_keys; k++) {
if (!candidate[k]) continue;
task_bits[(size_t)t * n_keys + k] = cursor;
cursor += (out->ranges[k] - out->nullable[k] + 63) / 64;
}
}
agg_compact_ctx_t c = { .key_cols = key_cols, .dp = out, .candidate = candidate,
.n_keys = n_keys, .rows = nrows, .tasks = tasks, .task_bits = task_bits };
if (tasks > 1) ray_pool_dispatch_n(pool, agg_compact_mark_fn, &c, tasks);
else agg_compact_mark_fn(&c, 0, 0, 1);
int64_t used[16];
int64_t total = 1;
bool fits = true;
for (uint32_t k = 0; k < n_keys; k++) {
int64_t rng = out->ranges[k];
if (candidate[k]) {
size_t words = (size_t)((rng - out->nullable[k] + 63) / 64);
uint64_t* acc = task_bits[k];
for (uint32_t t = 1; t < tasks; t++) {
const uint64_t* tb = task_bits[(size_t)t * n_keys + k];
for (size_t w = 0; w < words; w++) acc[w] |= tb[w];
}
int64_t n = 0;
for (size_t w = 0; w < words; w++) n += __builtin_popcountll(acc[w]);
used[k] = n;
rng = n + out->nullable[k];
if (rng <= 0) rng = 1;
} else used[k] = rng;
if (fits && total > dense_limit / rng) fits = false;
if (fits) total *= rng;
}
if (!fits) { ray_free_raw(bits); ray_free_raw(task_bits); return false; }
int64_t raw_ranges[16];
memcpy(raw_ranges, out->ranges, (size_t)n_keys * sizeof(int64_t));
for (uint32_t k = 0; k < n_keys; k++) {
if (!candidate[k]) continue;
int64_t raw = raw_ranges[k] - out->nullable[k];
if (used[k] >= raw) continue;
int32_t* remap = ray_alloc_raw((size_t)raw * sizeof(int32_t));
int64_t* inverse = ray_alloc_raw((size_t)(used[k] > 0 ? used[k] : 1) * sizeof(int64_t));
if (!remap || !inverse) {
ray_free_raw(remap); ray_free_raw(inverse);
ray_free_raw(bits); ray_free_raw(task_bits);
agg_dense_plan_free(out);
memcpy(out->ranges, raw_ranges, (size_t)n_keys * sizeof(int64_t));
return false;
}
const uint64_t* acc = task_bits[k];
int64_t next = 0;
for (int64_t code = 0; code < raw; code++) {
bool on = (acc[code >> 6] >> (code & 63)) & 1;
remap[code] = on ? (int32_t)next : -1;
if (on) inverse[next++] = out->mins[k] + code;
}
out->remap[k] = remap;
out->inverse[k] = inverse;
out->compacted = true;
out->ranges[k] = used[k] + out->nullable[k];
if (out->ranges[k] <= 0) out->ranges[k] = 1;
}
ray_free_raw(bits); ray_free_raw(task_bits);
return true;
}
bool agg_dense_plan(ray_t** key_cols, uint32_t n_keys,
const agg_vtable_t** vts, uint32_t n_aggs,
int64_t nrows, dense_plan_t* out) {
(void)vts; (void)n_aggs;
out->ok = false;
out->n_keys = n_keys;
out->compacted = false;
memset(out->remap, 0, sizeof(out->remap));
memset(out->inverse, 0, sizeof(out->inverse));
if (n_keys < 1 || n_keys > 16) return false;
for (uint32_t k = 0; k < n_keys; k++) {
ray_t* kc = key_cols[k];
switch (kc->type) {
case RAY_I64: case RAY_I32: case RAY_I16: case RAY_U8:
case RAY_BOOL: case RAY_DATE: case RAY_TIME:
case RAY_TIMESTAMP: case RAY_SYM: break;
default: return false;
}
out->nullable[k] = kc->type != RAY_SYM && ray_vec_may_have_nulls(kc);
out->nulls[k] = agg_key_null(kc->type);
if (nrows <= 0) return false;
if (kc->type == RAY_SYM) {
switch (kc->attrs & RAY_SYM_W_MASK) {
case RAY_SYM_W8:
if (nrows >= (int64_t)UINT8_MAX + 1) {
out->mins[k] = 0; out->ranges[k] = (int64_t)UINT8_MAX + 1; continue;
}
break;
case RAY_SYM_W16:
if (nrows >= (int64_t)UINT16_MAX + 1) {
out->mins[k] = 0; out->ranges[k] = (int64_t)UINT16_MAX + 1; continue;
}
break;
default: break;
}
int64_t dc = ray_sym_domain_count(ray_sym_vec_domain(kc));
if (dc > 0 && dc <= nrows) {
out->mins[k] = 0; out->ranges[k] = dc; continue;
}
}
int64_t mn, mx;
agg_key_bounds_parallel(kc, nrows, out->nullable[k], out->nulls[k], &mn, &mx);
if (!agg_dense_range(out, k, mn, mx)) return false;
}
int64_t total = 1;
int64_t dense_limit = nrows < (int64_t)UINT32_MAX
? nrows : (int64_t)UINT32_MAX;
bool overflow = false;
for (uint32_t k = 0; k < n_keys && !overflow; k++) {
int64_t rng = out->ranges[k];
if (rng <= 0) return false;
if (total > dense_limit / rng) overflow = true;
else total *= rng;
}
if (overflow || (n_keys >= 2 && total > AGG_COMPACT_TRY_SLOTS)) {
bool compacted_ok = n_keys >= 2 &&
agg_dense_plan_compact(key_cols, n_keys, nrows, dense_limit, out);
if (overflow && !compacted_ok) return false;
total = 1;
for (uint32_t k = 0; k < n_keys; k++) {
int64_t rng = out->ranges[k];
if (rng <= 0 || total > dense_limit / rng) { agg_dense_plan_free(out); return false; }
total *= rng;
}
}
total = 1;
for (uint32_t k = 0; k < n_keys; k++) {
out->strides[k] = total;
total *= out->ranges[k];
}
out->total_slots = total;
out->ok = true;
return true;
}
static const char* agg_name_suffix(uint16_t agg_op, size_t* slen_out) {
const char* sfx = ""; size_t slen = 0;
switch (agg_op) {
case OP_SUM: sfx = "_sum"; slen = 4; break;
case OP_PROD: sfx = "_prod"; slen = 5; break;
case OP_ALL: sfx = "_all"; slen = 4; break;
case OP_ANY: sfx = "_any"; slen = 4; break;
case OP_COUNT: sfx = "_count"; slen = 6; break;
case OP_AVG: sfx = "_mean"; slen = 5; break;
case OP_MIN: sfx = "_min"; slen = 4; break;
case OP_MAX: sfx = "_max"; slen = 4; break;
case OP_FIRST: sfx = "_first"; slen = 6; break;
case OP_LAST: sfx = "_last"; slen = 5; break;
case OP_STDDEV: sfx = "_stddev"; slen = 7; break;
case OP_STDDEV_POP: sfx = "_stddev_pop"; slen = 11; break;
case OP_VAR: sfx = "_var"; slen = 4; break;
case OP_VAR_POP: sfx = "_var_pop"; slen = 8; break;
case OP_MEDIAN: sfx = "_median"; slen = 7; break;
case OP_QUANTILE: sfx = "_quantile"; slen = 9; break;
case OP_MODE: sfx = "_mode"; slen = 5; break;
case OP_COV: sfx = "_cov"; slen = 4; break;
case OP_SCOV: sfx = "_scov"; slen = 5; break;
case OP_WSUM: sfx = "_wsum"; slen = 5; break;
case OP_WAVG: sfx = "_wavg"; slen = 5; break;
case OP_TOP_N: sfx = "_top"; slen = 4; break;
case OP_BOT_N: sfx = "_bot"; slen = 4; break;
default: break;
}
*slen_out = slen;
return sfx;
}
int64_t agg_result_col_name(int64_t in_sym, uint16_t agg_op) {
ray_t* name_atom = ray_sym_str(in_sym);
const char* base = name_atom ? ray_str_ptr(name_atom) : NULL;
size_t blen = base ? ray_str_len(name_atom) : 0;
size_t slen = 0;
const char* sfx = agg_name_suffix(agg_op, &slen);
char buf[256];
if (base && blen + slen < sizeof(buf)) {
memcpy(buf, base, blen);
memcpy(buf + blen, sfx, slen);
return ray_sym_intern(buf, blen + slen);
}
return in_sym;
}
typedef struct {
const char* source;
char* output;
const int64_t* rows;
size_t width;
char null_value[16];
_Atomic(bool) any_null;
} agg_gather_native_t;
static void agg_gather_native_run(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_gather_native_t* c = raw;
bool any_null = false;
#define GATHER_NATIVE(WIDTH) \
for (int64_t i = start; i < end; i++) { \
int64_t row = c->rows[i]; \
if (row < 0) { \
memcpy(c->output + (size_t)i * (WIDTH), c->null_value, (WIDTH)); \
any_null = true; \
} else memcpy(c->output + (size_t)i * (WIDTH), \
c->source + (size_t)row * (WIDTH), (WIDTH)); \
}
assert(c->width > 0 && c->width <= sizeof(c->null_value));
switch (c->width) {
case 1: GATHER_NATIVE(1); break;
case 2: GATHER_NATIVE(2); break;
case 4: GATHER_NATIVE(4); break;
case 8: GATHER_NATIVE(8); break;
case 16: GATHER_NATIVE(16); break;
default: GATHER_NATIVE(c->width); break;
}
#undef GATHER_NATIVE
if (any_null) atomic_store_explicit(&c->any_null, true, memory_order_relaxed);
}
ray_t* ray_group_gather(ray_t* src_col, const int64_t* first_row, int64_t n) {
if (src_col->type == RAY_LIST) {
ray_t* out = ray_list_new(n);
if (!out || RAY_IS_ERR(out)) return out;
for (int64_t i = 0; i < n; i++) {
ray_t* value = first_row[i] < 0 ? NULL : ray_list_get(src_col, first_row[i]);
out = ray_list_append(out, value);
}
return out;
}
if (src_col->type == RAY_STR) {
const char** ptrs = ray_alloc_raw((size_t)(n ? n : 1) * sizeof(char*));
uint32_t* lens = ray_alloc_raw((size_t)(n ? n : 1) * sizeof(uint32_t));
if (!ptrs || !lens) { ray_free_raw(ptrs); ray_free_raw(lens); return ray_error("oom", NULL); }
for (int64_t i = 0; i < n; i++) {
size_t len = 0;
ptrs[i] = first_row[i] < 0 ? "" : ray_str_vec_get(src_col, first_row[i], &len);
lens[i] = (uint32_t)len;
}
ray_t* out = ray_str_vec_from_parts(ptrs, lens, NULL, n);
ray_free_raw(ptrs); ray_free_raw(lens);
return out;
}
ray_t* out = col_vec_new(src_col, n);
if (!out || RAY_IS_ERR(out)) return out;
if (out->type == RAY_SYM)
ray_sym_vec_adopt_domain(out, sym_domain_rep(src_col));
out->len = n;
agg_gather_native_t c = {.source = ray_data(src_col), .output = ray_data(out),
.rows = first_row, .width = col_esz(src_col)};
#define GATHER_NULL(TYPE, VALUE) do { TYPE value = (VALUE); memcpy(c.null_value, &value, sizeof(value)); } while (0)
switch (src_col->type) {
case RAY_F64: GATHER_NULL(double, NULL_F64); break;
case RAY_F32: GATHER_NULL(float, NULL_F32); break;
case RAY_I64: case RAY_TIMESTAMP: GATHER_NULL(int64_t, NULL_I64); break;
case RAY_I32: case RAY_DATE: case RAY_TIME: GATHER_NULL(int32_t, NULL_I32); break;
case RAY_I16: GATHER_NULL(int16_t, NULL_I16); break;
default: break;
}
#undef GATHER_NULL
ray_pool_t* pool = ray_pool_get();
if (ray_pool_par_dispatch_ok(pool, n, RAY_PARALLEL_THRESHOLD))
ray_pool_dispatch(pool, agg_gather_native_run, &c, n);
else agg_gather_native_run(&c, 0, 0, n);
if (agg_cancelled()) { ray_release(out); return ray_error("cancel", NULL); }
if (ray_vec_may_have_nulls(src_col) ||
(atomic_load_explicit(&c.any_null, memory_order_relaxed) &&
src_col->type != RAY_BOOL && src_col->type != RAY_U8))
out->attrs |= RAY_ATTR_HAS_NULLS;
return out;
}
#include "core/pool.h"
#define AGG_SEL_CHUNK 2048
static ray_t* agg_sel_build_prefix(ray_t* sel) {
ray_rowsel_t* m = ray_rowsel_meta(sel);
uint32_t n_segs = m->n_segs;
const uint8_t* flags = ray_rowsel_flags(sel);
const uint32_t* offsets = ray_rowsel_offsets(sel);
int64_t nrows = m->nrows;
ray_t* block = ray_alloc((size_t)(n_segs + 1) * sizeof(int64_t));
if (!block) return NULL;
int64_t* prefix = (int64_t*)ray_data(block);
int64_t cum = 0;
for (uint32_t s = 0; s < n_segs; s++) {
prefix[s] = cum;
uint8_t f = flags[s];
if (f == RAY_SEL_NONE) continue;
if (f == RAY_SEL_ALL) {
int64_t base = (int64_t)s * RAY_MORSEL_ELEMS;
int64_t end = base + RAY_MORSEL_ELEMS;
if (end > nrows) end = nrows;
cum += end - base;
} else {
cum += offsets[s + 1] - offsets[s];
}
}
prefix[n_segs] = cum;
return block;
}
typedef struct {
ray_t* sel;
const uint8_t* flags;
const uint32_t* offsets;
const uint16_t* idx;
const int64_t* prefix;
int64_t nrows;
uint32_t n_segs;
int64_t remaining;
uint32_t seg;
int64_t seg_pos;
} agg_sel_cursor_t;
static void agg_sel_cursor_init(agg_sel_cursor_t* c, ray_t* sel,
const int64_t* prefix,
int64_t sel_start, int64_t sel_end) {
ray_rowsel_t* m = ray_rowsel_meta(sel);
c->sel = sel;
c->flags = ray_rowsel_flags(sel);
c->offsets = ray_rowsel_offsets(sel);
c->idx = ray_rowsel_idx(sel);
c->prefix = prefix;
c->nrows = m->nrows;
c->n_segs = m->n_segs;
c->remaining = sel_end - sel_start;
if (c->remaining <= 0) { c->seg = c->n_segs; c->seg_pos = 0; return; }
uint32_t lo = 0, hi = c->n_segs;
while (lo < hi) {
uint32_t mid = lo + (hi - lo) / 2;
if (prefix[mid + 1] <= sel_start) lo = mid + 1;
else hi = mid;
}
c->seg = lo;
c->seg_pos = sel_start - prefix[lo];
}
static int64_t agg_sel_cursor_next(agg_sel_cursor_t* c, int64_t* rows) {
int64_t n = 0;
while (c->remaining > 0 && n < AGG_SEL_CHUNK && c->seg < c->n_segs) {
uint32_t s = c->seg;
uint8_t f = c->flags[s];
if (f == RAY_SEL_NONE) { c->seg++; c->seg_pos = 0; continue; }
int64_t base = (int64_t)s * RAY_MORSEL_ELEMS;
int64_t seg_end = base + RAY_MORSEL_ELEMS;
if (seg_end > c->nrows) seg_end = c->nrows;
if (f == RAY_SEL_ALL) {
int64_t seg_sel = seg_end - base;
while (c->seg_pos < seg_sel && n < AGG_SEL_CHUNK && c->remaining > 0) {
rows[n++] = base + c->seg_pos;
c->seg_pos++; c->remaining--;
}
if (c->seg_pos >= seg_sel) { c->seg++; c->seg_pos = 0; }
} else {
const uint16_t* slice = c->idx + c->offsets[s];
int64_t seg_sel = c->offsets[s + 1] - c->offsets[s];
while (c->seg_pos < seg_sel && n < AGG_SEL_CHUNK && c->remaining > 0) {
rows[n++] = base + slice[c->seg_pos];
c->seg_pos++; c->remaining--;
}
if (c->seg_pos >= seg_sel) { c->seg++; c->seg_pos = 0; }
}
}
return n;
}
static inline void agg_sel_gather_vals(void* dst, const void* src, uint8_t esz,
const int64_t* rows, int64_t n) {
if (!src || esz == 0) return;
const char* s = (const char*)src;
switch (esz) {
case 8: {
int64_t* dd = (int64_t*)dst; const int64_t* ss = (const int64_t*)s;
for (int64_t i = 0; i < n; i++) dd[i] = ss[rows[i]];
break;
}
case 4: {
int32_t* dd = (int32_t*)dst; const int32_t* ss = (const int32_t*)s;
for (int64_t i = 0; i < n; i++) dd[i] = ss[rows[i]];
break;
}
case 2: {
int16_t* dd = (int16_t*)dst; const int16_t* ss = (const int16_t*)s;
for (int64_t i = 0; i < n; i++) dd[i] = ss[rows[i]];
break;
}
case 1: {
uint8_t* dd = (uint8_t*)dst; const uint8_t* ss = (const uint8_t*)s;
for (int64_t i = 0; i < n; i++) dd[i] = ss[rows[i]];
break;
}
default: {
char* dd = (char*)dst;
for (int64_t i = 0; i < n; i++) memcpy(dd + i * esz, s + rows[i] * esz, esz);
break;
}
}
}
typedef struct {
ray_t* hdr;
const void** key_data;
const void** val_data; const void** val2_data;
int64_t* agg_syms;
int8_t* val_types; int8_t* val2_types;
bool* val_hasnull; bool* val2_hasnull;
uint8_t* val_esz; uint8_t* val2_esz;
} agg_desc_t;
static bool agg_desc_init(agg_desc_t* d, ray_graph_t* g, ray_op_ext_t* ext,
ray_t* tbl, ray_t** key_cols) {
uint32_t nk = ext->n_keys, na = ext->n_aggs;
size_t n8 = (size_t)nk + 3u * na;
size_t n1 = 6u * na;
d->key_data = (const void**)scratch_alloc(&d->hdr, n8 * 8 + n1);
if (!d->key_data) return false;
d->val_data = d->key_data + nk;
d->val2_data = d->val_data + na;
d->agg_syms = (int64_t*)(d->val2_data + na);
d->val_types = (int8_t*)(d->agg_syms + na);
d->val2_types = d->val_types + na;
d->val_hasnull = (bool*)(d->val2_types + na);
d->val2_hasnull = d->val_hasnull + na;
d->val_esz = (uint8_t*)(d->val2_hasnull + na);
d->val2_esz = d->val_esz + na;
for (uint32_t k = 0; k < nk; k++) d->key_data[k] = ray_data(key_cols[k]);
for (uint32_t a = 0; a < na; a++) {
ray_op_ext_t* ie = find_ext(g, ext->agg_ins[a]);
d->agg_syms[a] = ie ? ie->sym : 0;
ray_t* vc = (ext->agg_ops[a] != OP_COUNT && ie) ? ray_table_get_col(tbl, ie->sym) : NULL;
d->val_data[a] = vc ? ray_data(vc) : NULL;
d->val_types[a] = vc ? vc->type : RAY_I64;
d->val_hasnull[a] = vc ? ray_vec_may_have_nulls(vc) : false;
d->val_esz[a] = vc ? col_esz(vc) : 0;
ray_t* vc2 = (ext->agg_ins2 && ext->agg_ins2[a] != RAY_OP_NONE)
? ray_table_get_col(tbl, find_ext(g, ext->agg_ins2[a])->sym) : NULL;
d->val2_data[a] = vc2 ? ray_data(vc2) : NULL;
d->val2_types[a] = vc2 ? vc2->type : RAY_I64;
d->val2_hasnull[a] = vc2 ? ray_vec_may_have_nulls(vc2) : false;
d->val2_esz[a] = vc2 ? col_esz(vc2) : 0;
}
return true;
}
static void agg_desc_free(agg_desc_t* d) { scratch_free(d->hdr); d->hdr = NULL; }
typedef struct {
ray_t* hdr;
const agg_vtable_t** vts;
size_t* off;
size_t block;
} agg_vo_t;
static bool agg_vo_init(agg_vo_t* vo, ray_graph_t* g, ray_op_ext_t* ext, ray_t* tbl) {
uint32_t na = ext->n_aggs;
vo->vts = (const agg_vtable_t**)scratch_alloc(&vo->hdr,
(size_t)na * (sizeof(void*) + sizeof(size_t)));
if (!vo->vts) return false;
vo->off = (size_t*)(vo->vts + na);
vo->block = 0;
for (uint32_t a = 0; a < na; a++) {
ray_op_ext_t* ie = find_ext(g, ext->agg_ins[a]);
ray_t* vc = (ext->agg_ops[a] != OP_COUNT && ie) ? ray_table_get_col(tbl, ie->sym) : NULL;
int8_t in_type = vc ? vc->type : RAY_I64;
vo->vts[a] = agg_resolve(ext->agg_ops[a], in_type);
vo->off[a] = vo->block;
vo->block += vo->vts[a]->state_size;
}
return true;
}
static void agg_vo_free(agg_vo_t* vo) { scratch_free(vo->hdr); vo->hdr = NULL; }
typedef struct {
uint32_t n_aggs;
const agg_vtable_t** vts;
const size_t* off;
size_t block;
const void** val_data; const int8_t* val_types; const bool* val_hasnull; const uint8_t* val_esz;
const void** val2_data; const int8_t* val2_types; const bool* val2_hasnull; const uint8_t* val2_esz;
} agg_valdesc_t;
typedef struct {
char** gv;
char** gy;
ray_t* hdr;
uint32_t n_aggs;
int oom;
} agg_sel_scratch_t;
static bool agg_sel_scratch_init(agg_sel_scratch_t* sc, uint32_t n_aggs) {
sc->hdr = NULL; sc->gv = NULL; sc->gy = NULL; sc->n_aggs = 0; sc->oom = 0;
sc->gv = (char**)scratch_calloc(&sc->hdr, (size_t)2u * n_aggs * sizeof(char*));
if (!sc->gv) return false;
sc->gy = sc->gv + n_aggs;
sc->n_aggs = n_aggs;
return true;
}
static void agg_sel_scratch_free(agg_sel_scratch_t* sc) {
for (uint32_t a = 0; a < sc->n_aggs; a++) { ray_free_raw(sc->gv[a]); ray_free_raw(sc->gy[a]); }
scratch_free(sc->hdr); sc->hdr = NULL; sc->gv = NULL; sc->gy = NULL; sc->n_aggs = 0;
}
static int agg_sel_accum_chunk(const agg_valdesc_t* vd, agg_sel_scratch_t* sc,
char* states, const uint32_t* gid,
const int64_t* rows, int64_t n) {
for (uint32_t a = 0; a < vd->n_aggs; a++) {
const agg_vtable_t* vt = vd->vts[a];
if (vt->update_batch2) {
uint8_t ez = vd->val_esz[a], ez2 = vd->val2_esz[a];
if (!sc->gv[a]) { sc->gv[a] = ray_alloc_raw((size_t)AGG_SEL_CHUNK * (ez ? ez : 1)); if (!sc->gv[a]) { sc->oom = 1; return -1; } }
if (!sc->gy[a]) { sc->gy[a] = ray_alloc_raw((size_t)AGG_SEL_CHUNK * (ez2 ? ez2 : 1)); if (!sc->gy[a]) { sc->oom = 1; return -1; } }
agg_sel_gather_vals(sc->gv[a], vd->val_data[a], ez, rows, n);
agg_sel_gather_vals(sc->gy[a], vd->val2_data[a], ez2, rows, n);
ray_valid_t vx = { sc->gv[a], vd->val_types[a], vd->val_hasnull[a] };
ray_valid_t vy = { sc->gy[a], vd->val2_types[a], vd->val2_hasnull[a] };
vt->update_batch2(states + vd->off[a], vd->block, gid,
sc->gv[a], sc->gy[a], &vx, &vy, n, NULL);
} else if (vd->val_data[a]) {
uint8_t ez = vd->val_esz[a];
if (!sc->gv[a]) { sc->gv[a] = ray_alloc_raw((size_t)AGG_SEL_CHUNK * (ez ? ez : 1)); if (!sc->gv[a]) { sc->oom = 1; return -1; } }
agg_sel_gather_vals(sc->gv[a], vd->val_data[a], ez, rows, n);
ray_valid_t valid = { sc->gv[a], vd->val_types[a], vd->val_hasnull[a] };
vt->update_batch(states + vd->off[a], vd->block, gid, sc->gv[a], &valid, n, NULL);
} else {
ray_valid_t valid = { NULL, vd->val_types[a], false };
vt->update_batch(states + vd->off[a], vd->block, gid, NULL, &valid, n, NULL);
}
}
return 0;
}
typedef struct {
ray_t** key_cols;
const void** key_data;
const bool* nullable;
const int64_t* nulls;
uint32_t n_keys;
ray_t* sel;
const int64_t* sel_prefix;
int64_t n_sel;
uint32_t tasks;
int64_t* task_bounds;
} agg_sel_bounds_ctx_t;
static void agg_sel_bounds_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_sel_bounds_ctx_t* c = raw;
int64_t rows[AGG_SEL_CHUNK];
for (int64_t task = start; task < end; task++) {
int64_t* mins = c->task_bounds + (2 * (size_t)task) * c->n_keys;
int64_t* maxs = mins + c->n_keys;
for (uint32_t k = 0; k < c->n_keys; k++) { mins[k] = INT64_MAX; maxs[k] = INT64_MIN; }
int64_t begin = c->n_sel / c->tasks * task;
int64_t limit = task + 1 == c->tasks ? c->n_sel : c->n_sel / c->tasks * (task + 1);
agg_sel_cursor_t cur;
agg_sel_cursor_init(&cur, c->sel, c->sel_prefix, begin, limit);
int64_t cn;
while ((cn = agg_sel_cursor_next(&cur, rows)) > 0) {
for (uint32_t k = 0; k < c->n_keys; k++) {
ray_t* kc = c->key_cols[k]; const void* d = c->key_data[k];
int64_t mn = mins[k], mx = maxs[k];
for (int64_t i = 0; i < cn; i++) {
int64_t v = agg_read_key_i64(kc, d, rows[i]);
if (c->nullable[k] && v == c->nulls[k]) continue;
if (v < mn) mn = v;
if (v > mx) mx = v;
}
mins[k] = mn; maxs[k] = mx;
}
}
}
}
static bool agg_dense_plan_sel(ray_t** key_cols, uint32_t n_keys, int64_t n_sel,
ray_t* sel, const int64_t* sel_prefix,
dense_plan_t* out) {
out->ok = false;
out->n_keys = n_keys;
out->compacted = false;
memset(out->remap, 0, sizeof(out->remap));
memset(out->inverse, 0, sizeof(out->inverse));
if (n_keys < 1 || n_keys > 16) return false;
if (n_sel <= 0) return false;
for (uint32_t k = 0; k < n_keys; k++) {
ray_t* kc = key_cols[k];
switch (kc->type) {
case RAY_I64: case RAY_I32: case RAY_I16: case RAY_U8:
case RAY_BOOL: case RAY_DATE: case RAY_TIME:
case RAY_TIMESTAMP: case RAY_SYM: break;
default: return false;
}
out->nullable[k] = kc->type != RAY_SYM && ray_vec_may_have_nulls(kc);
out->nulls[k] = agg_key_null(kc->type);
out->mins[k] = INT64_MAX; out->ranges[k] = 0;
}
ray_t* pre_hdr;
void* pre = scratch_alloc(&pre_hdr, 3u * (size_t)n_keys * 8);
if (!pre) return false;
int64_t* mins = (int64_t*)pre;
int64_t* maxs = mins + n_keys;
const void** key_data = (const void**)(maxs + n_keys);
for (uint32_t k = 0; k < n_keys; k++) { mins[k] = INT64_MAX; maxs[k] = INT64_MIN; }
for (uint32_t k = 0; k < n_keys; k++) key_data[k] = ray_data(key_cols[k]);
ray_pool_t* pool = ray_pool_get();
uint32_t tasks = pool && n_sel >= RAY_PARALLEL_THRESHOLD ? ray_pool_total_workers(pool) * 4 : 1;
if (tasks > RAY_POOL_INIT_TASKS) tasks = RAY_POOL_INIT_TASKS;
if ((int64_t)tasks > n_sel) tasks = (uint32_t)n_sel;
agg_sel_bounds_ctx_t bc = { .key_cols = key_cols, .key_data = key_data,
.nullable = out->nullable, .nulls = out->nulls, .n_keys = n_keys,
.sel = sel, .sel_prefix = sel_prefix, .n_sel = n_sel, .tasks = tasks };
ray_t* tb_hdr;
bc.task_bounds = (int64_t*)scratch_alloc(&tb_hdr, 2u * (size_t)tasks * n_keys * sizeof(int64_t));
if (!bc.task_bounds) { scratch_free(pre_hdr); return false; }
if (tasks > 1) ray_pool_dispatch_n(pool, agg_sel_bounds_fn, &bc, tasks);
else agg_sel_bounds_fn(&bc, 0, 0, 1);
for (uint32_t t = 0; t < tasks; t++)
for (uint32_t k = 0; k < n_keys; k++) {
int64_t mn = bc.task_bounds[(2 * (size_t)t) * n_keys + k];
int64_t mx = bc.task_bounds[(2 * (size_t)t + 1) * n_keys + k];
if (mn < mins[k]) mins[k] = mn;
if (mx > maxs[k]) maxs[k] = mx;
}
scratch_free(tb_hdr);
for (uint32_t k = 0; k < n_keys; k++) {
if (!agg_dense_range(out, k, mins[k], maxs[k])) { scratch_free(pre_hdr); return false; }
}
scratch_free(pre_hdr);
int64_t total = 1;
int64_t dense_limit = n_sel < (int64_t)UINT32_MAX
? n_sel : (int64_t)UINT32_MAX;
for (uint32_t k = 0; k < n_keys; k++) {
int64_t rng = out->ranges[k];
if (rng <= 0) return false;
if (total > dense_limit / rng) return false;
out->strides[k] = total;
total *= rng;
}
out->total_slots = total;
out->ok = true;
return true;
}
static inline uint64_t agg_tuple_hash(ray_t** key_cols, const void** key_data,
uint32_t n_keys, int64_t r) {
uint64_t h = 1469598103934665603ULL;
for (uint32_t k = 0; k < n_keys; k++) {
int64_t v = agg_read_key_i64(key_cols[k], key_data[k], r);
h ^= (uint64_t)v; h *= 1099511628211ULL;
}
h ^= h >> 33; h *= 0xff51afd7ed558ccdULL;
h ^= h >> 33; h *= 0xc4ceb9fe1a85ec53ULL;
h ^= h >> 33;
return h;
}
static inline int agg_tuple_eq(ray_t** key_cols, const void** key_data,
uint32_t n_keys, int64_t ra, int64_t rb) {
for (uint32_t k = 0; k < n_keys; k++)
if (agg_read_key_i64(key_cols[k], key_data[k], ra) !=
agg_read_key_i64(key_cols[k], key_data[k], rb))
return 0;
return 1;
}
typedef struct {
char* states;
int64_t* first_row;
uint64_t* occupied;
int oom;
bool ready;
bool eager;
int64_t slots;
} agg_dense_local_t;
typedef struct {
ray_t** key_cols;
const void** key_data;
uint32_t n_keys;
const dense_plan_t* dp;
const agg_vtable_t** vts;
const size_t* off;
size_t block;
uint32_t n_aggs;
const void** val_data; const int8_t* val_types; const bool* val_hasnull; const uint8_t* val_esz;
const void** val2_data; const int8_t* val2_types; const bool* val2_hasnull; const uint8_t* val2_esz;
agg_dense_local_t* locals;
ray_t* sel;
const int64_t* sel_prefix;
agg_valdesc_t vd;
int64_t task_rows;
uint32_t n_tasks;
} agg_dense_ctx_t;
static int agg_dense_local_init(agg_dense_local_t* loc, int64_t total_slots, size_t block) {
loc->oom = 0;
size_t state_bytes = (size_t)total_slots * block;
size_t row_bytes = (size_t)total_slots * sizeof(int64_t);
size_t bits_bytes = ((size_t)total_slots + 63) / 64 * sizeof(uint64_t);
loc->first_row = (int64_t*)(loc->states + state_bytes);
loc->occupied = (uint64_t*)(loc->states + state_bytes + row_bytes);
memset(loc->occupied, 0, bits_bytes);
loc->ready = true;
return 0;
}
static void agg_dense_slab_destroy_states(char* states, int64_t total_slots,
const agg_vtable_t** vts, const size_t* off,
size_t block, uint32_t n_aggs) {
if (!states) return;
for (uint32_t a = 0; a < n_aggs; a++)
if (vts[a]->destroy)
for (int64_t s = 0; s < total_slots; s++)
vts[a]->destroy(states + (size_t)s * block + off[a]);
}
static void agg_dense_local_destroy_states(agg_dense_local_t* loc,
const agg_vtable_t** vts, const size_t* off, size_t block, uint32_t n_aggs) {
if (!loc->ready) return;
for (uint32_t a = 0; a < n_aggs; a++)
if (vts[a]->destroy)
for (int64_t s = 0; s < loc->slots; s++)
if (loc->eager || (loc->occupied[s / 64] & (UINT64_C(1) << (s % 64))))
vts[a]->destroy(loc->states + (size_t)s * block + off[a]);
}
typedef struct {
agg_dense_local_t* locals;
int64_t slots;
const agg_vtable_t** vts;
const size_t* off;
size_t block;
uint32_t n_aggs, nw;
char* states;
int64_t* first;
} agg_dense_merge_ctx_t;
static void agg_dense_merge_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_dense_merge_ctx_t* c = raw;
bool copy = c->nw == 1;
for (uint32_t a = 0; a < c->n_aggs && copy; a++) copy = c->vts[a]->destroy == NULL;
if (copy) {
const agg_dense_local_t* loc = &c->locals[0];
for (int64_t s = start; s < end; s++) {
if (loc->occupied[s / 64] & (UINT64_C(1) << (s % 64))) {
c->first[s] = loc->first_row[s];
memcpy(c->states + (size_t)s * c->block, loc->states + (size_t)s * c->block, c->block);
} else {
c->first[s] = INT64_MAX;
for (uint32_t a = 0; a < c->n_aggs; a++)
c->vts[a]->init(c->states + (size_t)s * c->block + c->off[a]);
}
}
return;
}
for (int64_t s = start; s < end; s++) {
c->first[s] = INT64_MAX;
for (uint32_t a = 0; a < c->n_aggs; a++)
c->vts[a]->init(c->states + (size_t)s * c->block + c->off[a]);
}
for (uint32_t w = 0; w < c->nw; w++) {
agg_dense_local_t* loc = &c->locals[w];
for (int64_t word = start / 64; word <= (end - 1) / 64; word++) {
uint64_t bits = loc->occupied[word];
if (word == start / 64) bits &= UINT64_MAX << (start % 64);
if (word == (end - 1) / 64 && end % 64) bits &= (UINT64_C(1) << (end % 64)) - 1;
while (bits) {
int64_t s = word * 64 + __builtin_ctzll(bits);
bits &= bits - 1;
if (loc->first_row[s] < c->first[s]) c->first[s] = loc->first_row[s];
for (uint32_t a = 0; a < c->n_aggs; a++)
c->vts[a]->merge(c->states + (size_t)s * c->block + c->off[a],
loc->states + (size_t)s * c->block + c->off[a], NULL);
}
}
}
}
static inline bool agg_put_cell_value(ray_t* out, int64_t i, ray_t* cell);
static inline bool agg_finalize_value(const agg_vtable_t* vt, const void* state,
ray_t* out, int64_t i, int64_t param) {
if (vt->finalize_value)
return vt->finalize_value(state, (char*)ray_data(out) + (size_t)i * col_esz(out));
ray_t* cell = vt->finalize(state, NULL, param);
bool is_null = agg_put_cell_value(out, i, cell);
ray_release(cell);
return is_null;
}
bool agg_topn_threshold(const double* vals, int64_t n,
const ray_group_emit_filter_t* ef, double* thr) {
if (ef->top_count_take <= 0 || n <= ef->top_count_take) return false;
ray_t* hdr = NULL;
double* sv = (double*)scratch_alloc(&hdr, (size_t)n * sizeof(double));
if (!sv) return false;
memcpy(sv, vals, (size_t)n * sizeof(double));
int64_t k = ef->desc ? (n - ef->top_count_take) : (ef->top_count_take - 1);
int64_t lo = 0, hi = n - 1;
while (lo < hi) {
double pivot = sv[k];
int64_t i = lo, j = hi;
while (i <= j) {
while (sv[i] < pivot) i++;
while (sv[j] > pivot) j--;
if (i <= j) { double t = sv[i]; sv[i] = sv[j]; sv[j] = t; i++; j--; }
}
if (k <= j) hi = j;
else if (k >= i) lo = i;
else break;
}
*thr = sv[k];
scratch_free(hdr);
return true;
}
int64_t agg_topn_mark(const double* vals, int64_t n, const ray_group_emit_filter_t* ef,
bool have_thr, double thr, uint8_t* keep) {
int64_t kept = 0;
for (int64_t i = 0; i < n; i++) {
double v = vals[i];
bool ok = !(ef->min_count_exclusive > 0 && !(v > (double)ef->min_count_exclusive));
if (ok && have_thr && (ef->desc ? (v < thr) : (v > thr))) ok = false;
keep[i] = (uint8_t)ok;
kept += ok;
}
return kept;
}
int64_t agg_topn_keep(const double* vals, int64_t n,
const ray_group_emit_filter_t* ef, uint8_t* keep) {
if (n <= 0) return 0;
double thr = 0.0;
bool have_thr = agg_topn_threshold(vals, n, ef, &thr);
return agg_topn_mark(vals, n, ef, have_thr, thr, keep);
}
static bool agg_topn_union_threshold(const double* cand, int64_t nc, int64_t total,
const ray_group_emit_filter_t* ef, double* thr) {
if (ef->top_count_take <= 0 || total <= ef->top_count_take || nc <= 0) return false;
if (nc > ef->top_count_take) return agg_topn_threshold(cand, nc, ef, thr);
double worst = cand[0];
for (int64_t i = 1; i < nc; i++)
if (ef->desc ? cand[i] < worst : cand[i] > worst) worst = cand[i];
*thr = worst;
return true;
}
static void agg_topn_candidates(const double* vals, int64_t n, int64_t cap, uint8_t desc,
double* heap, int64_t* hn) {
int64_t h = 0;
#define CAND_WORSE(a, b) (desc ? ((a) < (b)) : ((a) > (b)))
for (int64_t i = 0; i < n; i++) {
double v = vals[i];
if (h < cap) {
int64_t j = h++;
heap[j] = v;
while (j > 0) {
int64_t par = (j - 1) / 2;
if (!CAND_WORSE(heap[j], heap[par])) break;
double t = heap[par]; heap[par] = heap[j]; heap[j] = t; j = par;
}
} else if (CAND_WORSE(heap[0], v)) {
heap[0] = v;
int64_t j = 0;
for (;;) {
int64_t l = 2 * j + 1, r = l + 1, m = j;
if (l < cap && CAND_WORSE(heap[l], heap[m])) m = l;
if (r < cap && CAND_WORSE(heap[r], heap[m])) m = r;
if (m == j) break;
double t = heap[m]; heap[m] = heap[j]; heap[j] = t; j = m;
}
}
}
#undef CAND_WORSE
*hn = h;
}
bool agg_group_values_f64(const agg_vtable_t* vt, const char* states,
size_t stride, size_t off, const int64_t* slots,
int64_t n, int64_t param, double* out) {
int8_t t = vt->out_type;
switch (t) {
case RAY_F64: case RAY_F32: case RAY_I64: case RAY_TIMESTAMP:
case RAY_I32: case RAY_DATE: case RAY_TIME: case RAY_I16:
case RAY_U8: case RAY_BOOL: break;
default: return false;
}
const double null_sink = -INFINITY;
ray_t* cell = ray_vec_new(t, 1);
if (!cell || RAY_IS_ERR(cell)) { if (cell) ray_error_free(cell); return false; }
cell->len = 1;
for (int64_t i = 0; i < n; i++) {
int64_t g = slots ? slots[i] : i;
const void* state = states + (size_t)g * stride + off;
bool is_null = agg_finalize_value(vt, state, cell, 0, param);
double v;
switch (t) {
case RAY_F64: v = ((const double*)ray_data(cell))[0]; break;
case RAY_F32: v = ((const float*)ray_data(cell))[0]; break;
case RAY_I64: case RAY_TIMESTAMP: v = (double)((const int64_t*)ray_data(cell))[0]; break;
case RAY_I32: case RAY_DATE: case RAY_TIME: v = (double)((const int32_t*)ray_data(cell))[0]; break;
case RAY_I16: v = (double)((const int16_t*)ray_data(cell))[0]; break;
default: v = (double)((const uint8_t*)ray_data(cell))[0]; break;
}
out[i] = is_null || v != v ? null_sink : v;
}
ray_release(cell);
return true;
}
typedef struct {
const agg_vtable_t* vt;
ray_t* out;
const char* states;
const int64_t* slots;
size_t block, off;
int64_t param;
_Atomic(bool) any_null;
} agg_dense_emit_ctx_t;
static void agg_dense_emit_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_dense_emit_ctx_t* c = raw;
bool any = false;
for (int64_t i = start; i < end; i++) {
const void* st = c->states + (size_t)(c->slots ? c->slots[i] : i) * c->block + c->off;
any |= agg_finalize_value(c->vt, st, c->out, i, c->param);
}
if (any) atomic_store_explicit(&c->any_null, true, memory_order_relaxed);
}
static inline int64_t agg_dense_slot(const agg_dense_ctx_t* c, int64_t r, bool compacted) {
int64_t slot = 0;
if (compacted) {
for (uint32_t k = 0; k < c->n_keys; k++)
slot += agg_dense_component(c->dp, k, agg_read_key_i64(c->key_cols[k], c->key_data[k], r)) * c->dp->strides[k];
} else {
for (uint32_t k = 0; k < c->n_keys; k++)
slot += agg_dense_component_raw(c->dp, k, agg_read_key_i64(c->key_cols[k], c->key_data[k], r)) * c->dp->strides[k];
}
return slot;
}
static inline bool agg_dense_first(agg_dense_local_t* loc, int64_t slot) {
uint64_t bit = UINT64_C(1) << (slot % 64);
uint64_t* word = &loc->occupied[slot / 64];
if (*word & bit) return false;
*word |= bit;
return true;
}
static void agg_dense_phaseA_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
agg_dense_ctx_t* c = vctx;
agg_dense_local_t* loc = &c->locals[wid];
if (loc->oom) return;
int64_t n = end - start;
if (n <= 0) return;
if (c->sel) {
int64_t rows[AGG_SEL_CHUNK];
uint32_t gid[AGG_SEL_CHUNK];
agg_sel_scratch_t sc;
if (!agg_sel_scratch_init(&sc, c->n_aggs)) { loc->oom = 1; return; }
agg_sel_cursor_t cur;
agg_sel_cursor_init(&cur, c->sel, c->sel_prefix, start, end);
int64_t cn;
const bool compacted = c->dp->compacted;
while ((cn = agg_sel_cursor_next(&cur, rows)) > 0) {
for (int64_t i = 0; i < cn; i++) {
int64_t r = rows[i];
int64_t slot = agg_dense_slot(c, r, compacted);
gid[i] = (uint32_t)slot;
if (agg_dense_first(loc, slot)) {
for (uint32_t a = 0; a < c->n_aggs; a++)
c->vts[a]->init(loc->states + (size_t)slot * c->block + c->off[a]);
loc->first_row[slot] = r;
}
if (r < loc->first_row[slot]) loc->first_row[slot] = r;
}
if (agg_sel_accum_chunk(&c->vd, &sc, loc->states, gid, rows, cn) != 0) {
loc->oom = 1; agg_sel_scratch_free(&sc); return;
}
}
agg_sel_scratch_free(&sc);
return;
}
uint32_t cgid[8192];
if (c->n_keys == 1) {
const void* kd = c->key_data[0];
int64_t kmin = c->dp->mins[0];
int64_t kstride = c->dp->strides[0];
bool nullable = c->dp->nullable[0];
int64_t null = c->dp->nulls[0], null_slot = c->dp->ranges[0] - 1;
int64_t* fr = loc->first_row;
uint32_t* cg = cgid;
#define DENSE_SLOT1_LAZY(LD) \
for (int64_t r = start; r < end; r++) { \
int64_t v = (int64_t)(LD); \
int64_t slot = nullable && v == null ? null_slot \
: (v - kmin) * kstride; \
cg[r - start] = (uint32_t)slot; \
if (agg_dense_first(loc, slot)) { \
for (uint32_t a = 0; a < c->n_aggs; a++) \
c->vts[a]->init(loc->states + (size_t)slot * c->block + c->off[a]); \
fr[slot] = r; \
} \
}
#define DENSE_SLOT1_EAGER(LD, SLOT) \
for (int64_t r = start; r < end; r++) { \
int64_t v = (int64_t)(LD); \
int64_t slot = (SLOT); \
cg[r - start] = (uint32_t)slot; \
if (r < fr[slot]) fr[slot] = r; \
}
#define DENSE_SLOT1(LD) do { \
if (loc->eager) { \
if (!nullable) { DENSE_SLOT1_EAGER(LD, v - kmin); } \
else { DENSE_SLOT1_EAGER(LD, v == null ? null_slot : v - kmin); } \
} else { DENSE_SLOT1_LAZY(LD); } \
} while (0)
switch (c->key_cols[0]->type) {
case RAY_I64: case RAY_TIMESTAMP: DENSE_SLOT1(((const int64_t*)kd)[r]); break;
case RAY_I32: case RAY_DATE: case RAY_TIME: DENSE_SLOT1(((const int32_t*)kd)[r]); break;
case RAY_I16: DENSE_SLOT1(((const int16_t*)kd)[r]); break;
case RAY_U8: case RAY_BOOL: DENSE_SLOT1(((const uint8_t*)kd)[r]); break;
case RAY_SYM:
switch (c->key_cols[0]->attrs & RAY_SYM_W_MASK) {
case RAY_SYM_W8: DENSE_SLOT1(((const uint8_t*)kd)[r]); break;
case RAY_SYM_W16: DENSE_SLOT1(((const uint16_t*)kd)[r]); break;
case RAY_SYM_W32: DENSE_SLOT1(((const uint32_t*)kd)[r]); break;
default: DENSE_SLOT1(((const int64_t*)kd)[r]); break;
}
break;
default:
for (int64_t r = start; r < end; r++) {
int64_t slot = agg_dense_slot(c, r, false);
cg[r - start] = (uint32_t)slot;
if (r < fr[slot]) fr[slot] = r;
}
}
#undef DENSE_SLOT1
#undef DENSE_SLOT1_LAZY
#undef DENSE_SLOT1_EAGER
} else {
#define DENSE_SLOT_MULTI(COMPACTED) \
for (int64_t r = start; r < end; r++) { \
int64_t slot = agg_dense_slot(c, r, COMPACTED); \
cgid[r - start] = (uint32_t)slot; \
if (agg_dense_first(loc, slot)) { \
for (uint32_t a = 0; a < c->n_aggs; a++) \
c->vts[a]->init(loc->states + (size_t)slot * c->block + c->off[a]); \
loc->first_row[slot] = r; \
} \
}
if (c->dp->compacted) { DENSE_SLOT_MULTI(true); }
else { DENSE_SLOT_MULTI(false); }
#undef DENSE_SLOT_MULTI
}
for (uint32_t a = 0; a < c->n_aggs; a++) {
if (c->vts[a]->update_batch2) {
const void* vx = (const char*)c->val_data[a] + (size_t)start * c->val_esz[a];
const void* vy = (const char*)c->val2_data[a] + (size_t)start * c->val2_esz[a];
ray_valid_t valid_x = { vx, c->val_types[a], c->val_hasnull[a] };
ray_valid_t valid_y = { vy, c->val2_types[a], c->val2_hasnull[a] };
c->vts[a]->update_batch2(loc->states + c->off[a], c->block, cgid,
vx, vy, &valid_x, &valid_y, n, NULL);
} else {
const void* vals = (c->val_data[a])
? (const void*)((const char*)c->val_data[a] + (size_t)start * c->val_esz[a])
: NULL;
ray_valid_t valid = { vals, c->val_types[a],
c->val_data[a] ? c->val_hasnull[a] : false };
c->vts[a]->update_batch(loc->states + c->off[a], c->block,
cgid, vals, &valid, n, NULL);
}
}
}
static void agg_dense_task_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_dense_ctx_t* c = raw;
int64_t chunk = c->task_rows / c->n_tasks;
for (int64_t task = start; task < end; task++) {
agg_dense_local_t* loc = &c->locals[task];
agg_dense_local_init(loc, loc->slots, c->block);
if (loc->eager) for (int64_t s = 0; s < loc->slots; s++) {
loc->first_row[s] = INT64_MAX;
for (uint32_t a = 0; a < c->n_aggs; a++)
c->vts[a]->init(loc->states + (size_t)s * c->block + c->off[a]);
}
int64_t begin = chunk * task;
int64_t limit = task + 1 == c->n_tasks ? c->task_rows : chunk * (task + 1);
if (c->sel) agg_dense_phaseA_fn(c, (uint32_t)task, begin, limit);
else for (int64_t row = begin; row < limit && !agg_cancelled(); row += 8192)
agg_dense_phaseA_fn(c, (uint32_t)task, row, limit - row < 8192 ? limit : row + 8192);
if (loc->eager) for (int64_t s = 0; s < loc->slots; s++)
if (loc->first_row[s] != INT64_MAX)
loc->occupied[s / 64] |= UINT64_C(1) << (s % 64);
}
}
typedef struct {
const dense_plan_t* plan;
const int64_t* occupied;
int64_t part_slots;
uint32_t bits;
ray_t* out;
uint32_t component;
} agg_dense_key_emit_t;
static void agg_dense_key_emit(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_dense_key_emit_t* c = raw;
void* data = ray_data(c->out);
for (int64_t r = start; r < end; r++) {
int64_t index = c->occupied[r];
int64_t slot = c->bits ? ((index % c->part_slots) << c->bits) + index / c->part_slots : index;
uint32_t k = c->component;
int64_t component = (slot / c->plan->strides[k]) % c->plan->ranges[k];
write_col_i64(data, r, agg_dense_code(c->plan, k, component), c->out->type, c->out->attrs);
}
}
typedef struct {
const agg_vtable_t* vt;
const char* states;
size_t block, off;
const int64_t* slots;
int64_t n, param;
const ray_group_emit_filter_t* ef;
double* vals;
uint8_t* keep;
uint32_t tasks;
double* cand;
int64_t* cand_n;
int64_t cap;
bool have_thr;
double thr;
int64_t* kept;
_Atomic(int) fail;
} agg_slots_topn_ctx_t;
static void agg_slots_topn_range(const agg_slots_topn_ctx_t* c, int64_t task, int64_t* begin, int64_t* end) {
*begin = c->n / c->tasks * task;
*end = task + 1 == (int64_t)c->tasks ? c->n : c->n / c->tasks * (task + 1);
}
static void agg_slots_vals_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_slots_topn_ctx_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t b, e;
agg_slots_topn_range(c, task, &b, &e);
if (!agg_group_values_f64(c->vt, c->states, c->block, c->off, c->slots + b,
e - b, c->param, c->vals + b)) {
atomic_store_explicit(&c->fail, 1, memory_order_relaxed);
continue;
}
if (c->cap > 0)
agg_topn_candidates(c->vals + b, e - b, c->cap, c->ef->desc,
c->cand + (size_t)task * c->cap, &c->cand_n[task]);
}
}
static void agg_slots_mark_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_slots_topn_ctx_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t b, e;
agg_slots_topn_range(c, task, &b, &e);
c->kept[task] = agg_topn_mark(c->vals + b, e - b, c->ef, c->have_thr, c->thr, c->keep + b);
}
}
static int64_t agg_slots_topn_select(ray_pool_t* pool, const agg_vtable_t* vt,
const char* states, size_t block, size_t off, const int64_t* slots, int64_t n,
int64_t param, const ray_group_emit_filter_t* ef, uint8_t* keep) {
if (n <= 0) return 0;
uint32_t tasks = pool && n >= RAY_PARALLEL_THRESHOLD ? ray_pool_total_workers(pool) * 4 : 1;
if (tasks > RAY_POOL_INIT_TASKS) tasks = RAY_POOL_INIT_TASKS;
if ((int64_t)tasks > n) tasks = (uint32_t)n;
int64_t cap = ef->top_count_take > 0 && n > ef->top_count_take ? ef->top_count_take : 0;
double* vals = ray_alloc_raw((size_t)n * sizeof(double));
double* cand = ray_alloc_raw((size_t)(cap > 0 ? cap * tasks : 1) * sizeof(double));
int64_t* cand_n = ray_calloc_raw((size_t)tasks * 2 * sizeof(int64_t));
int64_t kept = -1;
if (vals && cand && cand_n) {
agg_slots_topn_ctx_t c = { .vt = vt, .states = states, .block = block, .off = off,
.slots = slots, .n = n, .param = param, .ef = ef, .vals = vals, .keep = keep,
.tasks = tasks, .cand = cand, .cand_n = cand_n, .cap = cap, .kept = cand_n + tasks };
atomic_init(&c.fail, 0);
if (tasks > 1) ray_pool_dispatch_n(pool, agg_slots_vals_fn, &c, tasks);
else agg_slots_vals_fn(&c, 0, 0, 1);
if (!atomic_load_explicit(&c.fail, memory_order_relaxed)) {
if (cap > 0) {
int64_t nc = 0;
for (uint32_t t = 0; t < tasks; t++) {
memmove(cand + nc, cand + (size_t)t * cap, (size_t)cand_n[t] * sizeof(double));
nc += cand_n[t];
}
c.have_thr = agg_topn_union_threshold(cand, nc, n, ef, &c.thr);
}
if (tasks > 1) ray_pool_dispatch_n(pool, agg_slots_mark_fn, &c, tasks);
else agg_slots_mark_fn(&c, 0, 0, 1);
kept = 0;
for (uint32_t t = 0; t < tasks; t++) kept += c.kept[t];
}
}
ray_free_raw(vals); ray_free_raw(cand); ray_free_raw(cand_n);
return kept;
}
static ray_t* agg_dense_finish(ray_t** key_cols, int64_t* key_syms, ray_op_ext_t* ext,
ray_pool_t* pool, const agg_vo_t* vo, const agg_desc_t* d,
int64_t total_slots, char* gstates, int64_t* gfirst,
const dense_plan_t* key_plan, int64_t key_part_slots, uint32_t key_bits,
const ray_group_emit_filter_t* ef, int64_t group_limit) {
uint32_t n_keys = ext->n_keys, n_aggs = ext->n_aggs;
const agg_vtable_t** vts = vo->vts;
const size_t* off = vo->off;
size_t block = vo->block;
const int64_t* agg_syms = d->agg_syms;
int64_t ng = 0;
for (int64_t s = 0; s < total_slots; s++) if (gfirst[s] != INT64_MAX) ng++;
ray_profile_tick("dense: freed slabs and counted groups");
int64_t* occupied_slot = ray_alloc_raw((size_t)(ng > 0 ? ng : 1) * sizeof(int64_t));
int64_t* first_row_ordered = key_plan ? NULL : ray_alloc_raw((size_t)(ng > 0 ? ng : 1) * sizeof(int64_t));
if (!occupied_slot || (!key_plan && !first_row_ordered)) {
ray_free_raw(occupied_slot); ray_free_raw(first_row_ordered);
agg_dense_slab_destroy_states(gstates, total_slots, vts, off, block, n_aggs);
ray_free_raw(gstates); ray_free_raw(gfirst);
return ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
{ int64_t i = 0;
for (int64_t s = 0; s < total_slots; s++)
if (gfirst[s] != INT64_MAX) {
if (first_row_ordered) first_row_ordered[i] = gfirst[s];
occupied_slot[i] = s; i++;
}
}
if (group_limit > 0 && first_row_ordered && ng > 0) {
int64_t n_keep = ng < group_limit ? ng : group_limit;
int64_t* hkey = ray_alloc_raw((size_t)n_keep * sizeof(int64_t));
int64_t* hslot = ray_alloc_raw((size_t)n_keep * sizeof(int64_t));
if (!hkey || !hslot) {
ray_free_raw(hkey); ray_free_raw(hslot);
ray_free_raw(occupied_slot); ray_free_raw(first_row_ordered);
agg_dense_slab_destroy_states(gstates, total_slots, vts, off, block, n_aggs);
ray_free_raw(gstates); ray_free_raw(gfirst);
return ray_error("oom", NULL);
}
{
int64_t hn = 0;
for (int64_t i = 0; i < ng; i++) {
int64_t first = first_row_ordered[i], slot = occupied_slot[i];
if (hn < n_keep) {
int64_t j = hn++;
hkey[j] = first; hslot[j] = slot;
while (j > 0) {
int64_t par = (j - 1) / 2;
if (hkey[par] >= hkey[j]) break;
int64_t tk = hkey[par]; hkey[par] = hkey[j]; hkey[j] = tk;
int64_t ts = hslot[par]; hslot[par] = hslot[j]; hslot[j] = ts;
j = par;
}
} else if (first < hkey[0]) {
hkey[0] = first; hslot[0] = slot;
int64_t j = 0;
for (;;) {
int64_t l = 2 * j + 1, rr = l + 1, m = j;
if (l < n_keep && hkey[l] > hkey[m]) m = l;
if (rr < n_keep && hkey[rr] > hkey[m]) m = rr;
if (m == j) break;
int64_t tk = hkey[m]; hkey[m] = hkey[j]; hkey[j] = tk;
int64_t ts = hslot[m]; hslot[m] = hslot[j]; hslot[j] = ts;
j = m;
}
}
}
for (int64_t end = hn - 1; end > 0; end--) {
int64_t tk = hkey[0]; hkey[0] = hkey[end]; hkey[end] = tk;
int64_t ts = hslot[0]; hslot[0] = hslot[end]; hslot[end] = ts;
int64_t j = 0;
for (;;) {
int64_t l = 2 * j + 1, rr = l + 1, m = j;
if (l < end && hkey[l] > hkey[m]) m = l;
if (rr < end && hkey[rr] > hkey[m]) m = rr;
if (m == j) break;
tk = hkey[m]; hkey[m] = hkey[j]; hkey[j] = tk;
ts = hslot[m]; hslot[m] = hslot[j]; hslot[j] = ts;
j = m;
}
}
for (int64_t i = 0; i < hn; i++) { occupied_slot[i] = hslot[i]; first_row_ordered[i] = hkey[i]; }
ng = hn;
}
ray_free_raw(hkey); ray_free_raw(hslot);
}
if (ef && ng > 0) {
uint8_t* keep = ray_alloc_raw((size_t)ng);
int64_t kept = keep ? agg_slots_topn_select(pool, vts[ef->agg_index], gstates, block,
off[ef->agg_index], occupied_slot, ng,
ext->agg_k ? ext->agg_k[ef->agg_index] : 0, ef, keep) : -1;
if (kept >= 0) {
int64_t w = 0;
for (int64_t i = 0; i < ng; i++) if (keep[i]) {
occupied_slot[w] = occupied_slot[i];
if (first_row_ordered) first_row_ordered[w] = first_row_ordered[i];
w++;
}
ng = kept;
route_stats.topn_native = true;
route_stats.topn_kept = kept;
}
ray_free_raw(keep);
ray_profile_tick("dense: selected top-N groups");
}
ray_t* result = ray_table_new(n_keys + n_aggs);
if (!result || RAY_IS_ERR(result)) {
agg_dense_slab_destroy_states(gstates, total_slots, vts, off, block, n_aggs);
ray_free_raw(occupied_slot); ray_free_raw(first_row_ordered); ray_free_raw(gstates); ray_free_raw(gfirst);
return result ? result : ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
for (uint32_t k = 0; k < n_keys; k++) {
ray_t* kc = key_plan ? col_vec_new(key_cols[k], ng)
: ray_group_gather(key_cols[k], first_row_ordered, ng);
if (!kc || RAY_IS_ERR(kc)) {
agg_dense_slab_destroy_states(gstates, total_slots, vts, off, block, n_aggs);
ray_free_raw(occupied_slot); ray_free_raw(first_row_ordered); ray_free_raw(gstates); ray_free_raw(gfirst);
ray_release(result); return kc ? kc : ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
if (key_plan) {
kc->len = ng;
if (ray_vec_may_have_nulls(key_cols[k])) kc->attrs |= RAY_ATTR_HAS_NULLS;
if (kc->type == RAY_SYM) ray_sym_vec_adopt_domain(kc, sym_domain_rep(key_cols[k]));
agg_dense_key_emit_t emit = {key_plan, occupied_slot, key_part_slots, key_bits, kc, k};
ray_pool_dispatch(pool, agg_dense_key_emit, &emit, ng);
}
result = ray_table_add_col(result, key_syms[k], kc);
ray_release(kc);
}
for (uint32_t a = 0; a < n_aggs; a++) {
bool is_list = (vts[a]->out_type == RAY_LIST);
ray_t* out = is_list ? ray_list_new(ng)
: ray_vec_new(vts[a]->out_type, ng);
if (!out || RAY_IS_ERR(out)) {
agg_dense_slab_destroy_states(gstates, total_slots, vts, off, block, n_aggs);
ray_free_raw(occupied_slot); ray_free_raw(first_row_ordered); ray_free_raw(gstates); ray_free_raw(gfirst);
ray_release(result); return out ? out : ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
out->len = ng;
int64_t kparam = (ext->agg_k ? ext->agg_k[a] : 0);
if (!is_list) {
agg_dense_emit_ctx_t emit = { .vt = vts[a], .out = out, .states = gstates,
.slots = occupied_slot, .block = block, .off = off[a], .param = kparam, .any_null = false };
ray_pool_dispatch(pool, agg_dense_emit_fn, &emit, ng);
if (atomic_load_explicit(&emit.any_null, memory_order_relaxed)) out->attrs |= RAY_ATTR_HAS_NULLS;
} else {
for (int64_t i = 0; i < ng; i++) {
ray_t* cell = vts[a]->finalize(gstates + (size_t)occupied_slot[i] * block + off[a], NULL, kparam);
out = ray_list_set(out, i, cell);
ray_release(cell);
}
}
int64_t agg_name = agg_result_col_name(agg_syms[a], ext->agg_ops[a]);
result = ray_table_add_col(result, agg_name, out);
ray_release(out);
}
agg_dense_slab_destroy_states(gstates, total_slots, vts, off, block, n_aggs);
ray_free_raw(occupied_slot); ray_free_raw(first_row_ordered); ray_free_raw(gstates); ray_free_raw(gfirst);
if (result && !RAY_IS_ERR(result) && agg_cancelled()) { ray_release(result); return ray_error("cancel", NULL); }
return result;
}
typedef struct {
uint32_t part;
uint64_t begin, end;
int64_t base;
} agg_dense_partition_task_t;
typedef struct {
ray_t* key;
ray_t** keys;
const void** key_data;
ray_t* selection_indices;
const int64_t* selected_rows;
const dense_plan_t* plan;
agg_valdesc_t values;
int64_t rows, part_slots;
uint32_t sources, parts, bits, n_tasks;
agg_dense_partition_task_t* tasks;
uint64_t* counts;
uint64_t* starts;
uint32_t* gids;
char* payload;
size_t record_size;
size_t* value_offsets;
size_t* value2_offsets;
char* states;
int64_t* first;
_Atomic(bool) failed;
} agg_dense_partition_ctx_t;
static void agg_dense_partition_count(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_dense_partition_ctx_t* c = raw;
const void* data = ray_data(c->key);
for (int64_t task = start; task < end; task++) {
int64_t begin = c->rows / c->sources * task;
int64_t limit = task + 1 == c->sources ? c->rows : c->rows / c->sources * (task + 1);
uint64_t* count = c->counts + task * c->parts;
if (c->plan->n_keys > 1) {
#define PART_SLOT_MULTI(COMPONENT) \
for (int64_t r = begin; r < limit; r++) { \
int64_t source = c->selected_rows ? c->selected_rows[r] : r; \
int64_t slot = 0; \
for (uint32_t k = 0; k < c->plan->n_keys; k++) \
slot += COMPONENT(c->plan, k, agg_read_key_i64(c->keys[k], c->key_data[k], source)) * c->plan->strides[k]; \
c->gids[r] = (uint32_t)slot; \
count[slot & (c->parts - 1)]++; \
}
if (c->plan->compacted) { PART_SLOT_MULTI(agg_dense_component); }
else { PART_SLOT_MULTI(agg_dense_component_raw); }
#undef PART_SLOT_MULTI
continue;
}
#define PART_COUNT(T) do { \
const T* p = data; \
for (int64_t r = begin; r < limit; r++) { \
uint32_t slot = (uint32_t)agg_dense_component_raw(c->plan, 0, (int64_t)p[c->selected_rows ? c->selected_rows[r] : r]); \
c->gids[r] = slot; \
count[slot & (c->parts - 1)]++; \
} \
} while (0)
switch (c->key->type) {
case RAY_I64: case RAY_TIMESTAMP: PART_COUNT(int64_t); break;
case RAY_I32: case RAY_DATE: case RAY_TIME: PART_COUNT(int32_t); break;
case RAY_I16: PART_COUNT(int16_t); break;
case RAY_U8: case RAY_BOOL: PART_COUNT(uint8_t); break;
case RAY_SYM:
switch (c->key->attrs & RAY_SYM_W_MASK) {
case RAY_SYM_W8: PART_COUNT(uint8_t); break;
case RAY_SYM_W16: PART_COUNT(uint16_t); break;
case RAY_SYM_W32: PART_COUNT(uint32_t); break;
default: PART_COUNT(int64_t); break;
}
break;
}
#undef PART_COUNT
}
}
static void agg_dense_partition_field(agg_dense_partition_ctx_t* c, const void* data,
uint8_t width, size_t field, const uint64_t* starts, int64_t begin, int64_t end) {
uint64_t cursor[RAY_POOL_INIT_TASKS / 2];
assert(c->parts <= RAY_POOL_INIT_TASKS / 2);
memcpy(cursor, starts, c->parts * sizeof(*cursor));
#define SCATTER_FIELD(W) do { \
for (int64_t r = begin; r < end; r++) { \
uint32_t part = c->gids[r] & (c->parts - 1); \
memcpy(c->payload + cursor[part]++ * c->record_size + field, \
(const char*)data + (size_t)(c->selected_rows ? c->selected_rows[r] : r) * (W), (W)); \
} \
} while (0)
switch (width) {
case 1: SCATTER_FIELD(1); break;
case 2: SCATTER_FIELD(2); break;
case 4: SCATTER_FIELD(4); break;
case 8: SCATTER_FIELD(8); break;
}
#undef SCATTER_FIELD
}
static void agg_dense_partition_scatter(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_dense_partition_ctx_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t begin = c->rows / c->sources * task;
int64_t limit = task + 1 == c->sources ? c->rows : c->rows / c->sources * (task + 1);
const uint64_t* starts = c->counts + task * c->parts;
uint64_t cursor[RAY_POOL_INIT_TASKS / 2];
assert(c->parts <= RAY_POOL_INIT_TASKS / 2);
memcpy(cursor, starts, c->parts * sizeof(*cursor));
const agg_valdesc_t* vd = &c->values;
if (vd->n_aggs == 1 && vd->val_data[0] && !vd->val2_data[0]) {
#define SCATTER_UNARY(W) do { \
for (int64_t r = begin; r < limit; r++) { \
uint32_t slot = c->gids[r]; \
uint32_t gid = slot >> c->bits, part = slot & (c->parts - 1); \
char* dst = c->payload + cursor[part]++ * (4 + (W)); \
memcpy(dst, &gid, 4); \
memcpy(dst + 4, (const char*)vd->val_data[0] + (size_t)(c->selected_rows ? c->selected_rows[r] : r) * (W), (W)); \
} \
} while (0)
switch (vd->val_esz[0]) {
case 1: SCATTER_UNARY(1); break;
case 2: SCATTER_UNARY(2); break;
case 4: SCATTER_UNARY(4); break;
case 8: SCATTER_UNARY(8); break;
}
#undef SCATTER_UNARY
} else {
int64_t chunk_rows = (64 * 1024) / c->record_size;
if (chunk_rows > AGG_SEL_CHUNK) chunk_rows = AGG_SEL_CHUNK;
if (chunk_rows < 1) chunk_rows = 1;
uint64_t chunk_starts[RAY_POOL_INIT_TASKS / 2];
for (int64_t chunk = begin; chunk < limit; chunk += chunk_rows) {
int64_t chunk_end = limit - chunk < chunk_rows ? limit : chunk + chunk_rows;
memcpy(chunk_starts, cursor, c->parts * sizeof(*cursor));
for (int64_t r = chunk; r < chunk_end; r++) {
uint32_t slot = c->gids[r], part = slot & (c->parts - 1), gid = slot >> c->bits;
char* dst = c->payload + cursor[part]++ * c->record_size;
memcpy(dst, &gid, 4);
}
size_t emitted = sizeof(uint32_t);
for (uint32_t a = 0; a < vd->n_aggs; a++) {
if (vd->val_data[a] && c->value_offsets[a] >= emitted) {
agg_dense_partition_field(c, vd->val_data[a], vd->val_esz[a], c->value_offsets[a], chunk_starts, chunk, chunk_end);
emitted = c->value_offsets[a] + vd->val_esz[a];
}
if (vd->val2_data[a] && c->value2_offsets[a] >= emitted) {
agg_dense_partition_field(c, vd->val2_data[a], vd->val2_esz[a], c->value2_offsets[a], chunk_starts, chunk, chunk_end);
emitted = c->value2_offsets[a] + vd->val2_esz[a];
}
}
}
}
}
}
static void agg_dense_payload_gather(char* dst, const char* src, size_t stride, uint8_t width, int64_t n) {
#define PAYLOAD_GATHER(W) for (int64_t i = 0; i < n; i++) \
memcpy(dst + (size_t)i * (W), src + (size_t)i * stride, (W))
switch (width) {
case 1: PAYLOAD_GATHER(1); break;
case 2: PAYLOAD_GATHER(2); break;
case 4: PAYLOAD_GATHER(4); break;
case 8: PAYLOAD_GATHER(8); break;
default: for (int64_t i = 0; i < n; i++) memcpy(dst + (size_t)i * width, src + (size_t)i * stride, width);
}
#undef PAYLOAD_GATHER
}
static int agg_dense_payload_accum(const agg_dense_partition_ctx_t* c, agg_sel_scratch_t* sc,
char* states, const uint32_t* gids, uint64_t start, int64_t n) {
const agg_valdesc_t* vd = &c->values;
const char* records = c->payload + start * c->record_size;
for (uint32_t a = 0; a < vd->n_aggs; a++) {
if (vd->val_data[a]) {
if (!sc->gv[a]) sc->gv[a] = ray_alloc_raw((size_t)AGG_SEL_CHUNK * vd->val_esz[a]);
if (!sc->gv[a]) return -1;
agg_dense_payload_gather(sc->gv[a], records + c->value_offsets[a], c->record_size, vd->val_esz[a], n);
}
if (vd->val2_data[a]) {
if (!sc->gy[a]) sc->gy[a] = ray_alloc_raw((size_t)AGG_SEL_CHUNK * vd->val2_esz[a]);
if (!sc->gy[a]) return -1;
agg_dense_payload_gather(sc->gy[a], records + c->value2_offsets[a], c->record_size, vd->val2_esz[a], n);
}
ray_valid_t vx = {sc->gv[a], vd->val_types[a], vd->val_data[a] && vd->val_hasnull[a]};
ray_valid_t vy = {sc->gy[a], vd->val2_types[a], vd->val2_hasnull[a]};
if (vd->vts[a]->update_batch2)
vd->vts[a]->update_batch2(states + vd->off[a], vd->block, gids, sc->gv[a], sc->gy[a], &vx, &vy, n, NULL);
else vd->vts[a]->update_batch(states + vd->off[a], vd->block, gids, sc->gv[a], &vx, n, NULL);
}
return 0;
}
static void agg_dense_partition_reduce(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_dense_partition_ctx_t* c = raw;
const agg_valdesc_t* vd = &c->values;
agg_sel_scratch_t scratch;
if (!agg_sel_scratch_init(&scratch, vd->n_aggs)) {
atomic_store_explicit(&c->failed, true, memory_order_relaxed);
return;
}
uint32_t gids[AGG_SEL_CHUNK];
for (int64_t task = start; task < end && !agg_cancelled(); task++) {
const agg_dense_partition_task_t* work = &c->tasks[task];
int64_t base = work->base;
char* states = c->states + (size_t)base * vd->block;
int64_t* first = c->first + base;
for (int64_t slot = 0; slot < c->part_slots; slot++) {
first[slot] = INT64_MAX;
for (uint32_t a = 0; a < vd->n_aggs; a++)
vd->vts[a]->init(states + (size_t)slot * vd->block + vd->off[a]);
}
for (uint64_t at = work->begin; at < work->end; at += AGG_SEL_CHUNK) {
int64_t n = work->end - at < AGG_SEL_CHUNK ? work->end - at : AGG_SEL_CHUNK;
for (int64_t i = 0; i < n; i++) {
uint32_t gid;
memcpy(&gid, c->payload + (at + i) * c->record_size, 4);
gids[i] = gid;
first[gid] = 0;
}
if (agg_dense_payload_accum(c, &scratch, states, gids, at, n) != 0) {
atomic_store_explicit(&c->failed, true, memory_order_relaxed);
break;
}
}
}
agg_sel_scratch_free(&scratch);
}
static void agg_dense_partition_merge(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_dense_partition_ctx_t* c = raw;
const agg_valdesc_t* vd = &c->values;
for (int64_t part = start; part < end; part++) {
int64_t dst = part * c->part_slots;
for (uint32_t task = 0; task < c->n_tasks; task++) {
const agg_dense_partition_task_t* work = &c->tasks[task];
if (work->part != part || work->base == dst) continue;
for (int64_t slot = 0; slot < c->part_slots; slot++) {
int64_t src = work->base + slot, target = dst + slot;
if (c->first[src] == INT64_MAX) continue;
if (c->first[src] < c->first[target]) c->first[target] = c->first[src];
for (uint32_t a = 0; a < vd->n_aggs; a++)
vd->vts[a]->merge(c->states + (size_t)target * vd->block + vd->off[a],
c->states + (size_t)src * vd->block + vd->off[a], NULL);
}
}
}
}
static size_t agg_partition_record(const agg_desc_t* d, uint32_t n,
size_t* x_offsets, size_t* y_offsets) {
size_t bytes = sizeof(uint32_t);
for (uint32_t f = 0; f < 2 * n; f++) {
uint32_t a = f / 2;
const void* data = f % 2 ? d->val2_data[a] : d->val_data[a];
uint8_t width = f % 2 ? d->val2_esz[a] : d->val_esz[a];
size_t offset = bytes;
bool found = false;
for (uint32_t prev = 0; data && prev < f; prev++) {
uint32_t b = prev / 2;
const void* other = prev % 2 ? d->val2_data[b] : d->val_data[b];
uint8_t size = prev % 2 ? d->val2_esz[b] : d->val_esz[b];
if (other == data && size == width) {
if (x_offsets) offset = prev % 2 ? y_offsets[b] : x_offsets[b];
found = true; break;
}
}
if (data && !found) bytes += width;
if (x_offsets) (f % 2 ? y_offsets : x_offsets)[a] = offset;
}
return bytes;
}
static uint32_t agg_dense_partition_parts(uint32_t sources, int64_t slots) {
_Static_assert(RAY_POOL_INIT_TASKS >= 2 &&
(RAY_POOL_INIT_TASKS & (RAY_POOL_INIT_TASKS - 1)) == 0,
"dense partition cursor capacity must be a power of two");
uint32_t parts = 1;
while (parts < RAY_POOL_INIT_TASKS / 2 &&
(parts < sources * 4 || (int64_t)parts * 32768 < slots)) parts *= 2;
return parts;
}
static ray_t* agg_dense_partitioned(ray_t** key_cols, int64_t* key_syms, ray_op_ext_t* ext,
ray_pool_t* pool, const dense_plan_t* plan, int64_t rows,
const agg_vo_t* vo, const agg_desc_t* d, ray_t* selection,
const ray_group_emit_filter_t* ef) {
uint32_t workers = ray_pool_total_workers(pool);
uint32_t sources = workers;
if (sources > RAY_POOL_INIT_TASKS / 2) sources = RAY_POOL_INIT_TASKS / 2;
uint32_t parts = agg_dense_partition_parts(workers, plan->total_slots);
uint32_t bits = (uint32_t)__builtin_ctz(parts);
uint32_t split_budget = sources * 4 < parts ? sources * 4 : parts;
int64_t part_slots = ((plan->total_slots + parts - 1) / parts + 7) & ~INT64_C(7);
int64_t slots = part_slots * parts;
agg_dense_partition_ctx_t c = {
.key = key_cols[0], .keys = key_cols, .key_data = d->key_data,
.plan = plan, .rows = rows, .part_slots = part_slots,
.sources = sources, .parts = parts, .bits = bits, .failed = false,
.values = { .n_aggs = ext->n_aggs, .vts = vo->vts, .off = vo->off, .block = vo->block,
.val_data = d->val_data, .val_types = d->val_types, .val_hasnull = d->val_hasnull, .val_esz = d->val_esz,
.val2_data = d->val2_data, .val2_types = d->val2_types, .val2_hasnull = d->val2_hasnull, .val2_esz = d->val2_esz }
};
if (selection) {
c.selection_indices = ray_rowsel_to_indices(selection);
if (!c.selection_indices || RAY_IS_ERR(c.selection_indices)) goto failed;
c.selected_rows = ray_data(c.selection_indices);
}
c.counts = ray_calloc_raw(((size_t)sources * parts + parts + 1) * sizeof(uint64_t));
c.gids = ray_alloc_raw((size_t)rows * sizeof(uint32_t));
c.value_offsets = ray_alloc_raw((size_t)ext->n_aggs * 2 * sizeof(size_t));
if (!c.value_offsets) goto failed;
c.value2_offsets = c.value_offsets + ext->n_aggs;
c.record_size = agg_partition_record(d, ext->n_aggs, c.value_offsets, c.value2_offsets);
c.payload = ray_alloc_raw((size_t)rows * c.record_size);
c.tasks = ray_alloc_raw((size_t)(parts + split_budget) * sizeof(*c.tasks));
if (!c.counts || !c.gids || !c.tasks || !c.payload) goto failed;
c.starts = c.counts + (size_t)sources * parts;
ray_pool_dispatch_n(pool, agg_dense_partition_count, &c, sources);
if (agg_cancelled()) goto failed;
uint64_t offset = 0;
for (uint32_t part = 0; part < parts; part++) {
c.starts[part] = offset;
for (uint32_t task = 0; task < sources; task++) {
size_t at = (size_t)task * parts + part;
uint64_t n = c.counts[at]; c.counts[at] = offset; offset += n;
}
}
c.starts[parts] = offset;
uint64_t grain = ((uint64_t)rows + split_budget - 1) / split_budget;
int64_t extra_slots = 0;
for (uint32_t part = 0; part < parts; part++) {
uint64_t begin = c.starts[part], end = c.starts[part + 1];
do {
uint64_t limit = end - begin < grain ? end : begin + grain;
int64_t base = begin == c.starts[part] ? (int64_t)part * part_slots : slots + extra_slots;
if (base >= slots) extra_slots += part_slots;
c.tasks[c.n_tasks++] = (agg_dense_partition_task_t){part, begin, limit, base};
begin = limit;
} while (begin < end);
}
c.states = ray_alloc_raw((size_t)(slots + extra_slots) * vo->block);
c.first = ray_alloc_raw((size_t)(slots + extra_slots) * sizeof(int64_t));
if (!c.states || !c.first) goto failed;
ray_profile_tick("dense partition: histogram");
ray_pool_dispatch_n(pool, agg_dense_partition_scatter, &c, sources);
if (agg_cancelled()) goto failed;
ray_profile_tick("dense partition: scattered rows");
ray_pool_dispatch_n(pool, agg_dense_partition_reduce, &c, c.n_tasks);
if (agg_cancelled() || atomic_load_explicit(&c.failed, memory_order_relaxed)) goto failed;
ray_profile_tick("dense partition: reduced groups");
ray_pool_dispatch_n(pool, agg_dense_partition_merge, &c, parts);
if (agg_cancelled()) goto failed;
ray_profile_tick("dense partition: merged split partitions");
ray_free_raw(c.counts); ray_free_raw(c.gids); ray_free_raw(c.tasks);
ray_free_raw(c.payload); ray_free_raw(c.value_offsets); ray_release(c.selection_indices);
route_stats.dense_strategy = AGG_DENSE_PARTITIONED;
route_stats.dense_tasks = c.n_tasks;
route_stats.dense_local_slots = slots + extra_slots;
return agg_dense_finish(key_cols, key_syms, ext, pool, vo, d, slots, c.states, c.first, plan, part_slots, bits, ef, 0);
failed:
ray_free_raw(c.counts); ray_free_raw(c.gids); ray_free_raw(c.tasks);
ray_free_raw(c.payload); ray_free_raw(c.value_offsets); ray_release(c.selection_indices); ray_free_raw(c.states); ray_free_raw(c.first);
return ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
typedef struct {
ray_t* key;
const dense_plan_t* plan;
const agg_vo_t* layout;
const agg_desc_t* desc;
uint32_t n_aggs;
char* states;
_Atomic(uint64_t)* occupied;
int64_t* first;
} agg_dense_shared_ctx_t;
static void agg_dense_shared_init(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_dense_shared_ctx_t* c = raw;
const agg_vo_t* vo = c->layout;
for (int64_t s = start; s < end; s++)
for (uint32_t a = 0; a < c->n_aggs; a++)
vo->vts[a]->init(c->states + (size_t)s * vo->block + vo->off[a]);
}
static void agg_dense_shared_rows(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_dense_shared_ctx_t* c = raw;
const agg_vo_t* vo = c->layout;
const agg_desc_t* d = c->desc;
const void* data = ray_data(c->key);
uint32_t gids[8192];
for (int64_t begin = start; begin < end && !agg_cancelled(); begin += 8192) {
int64_t n = end - begin < 8192 ? end - begin : 8192;
#define SHARED_GIDS(T) do { \
const T* p = data; \
for (int64_t i = 0; i < n; i++) { \
uint32_t slot = (uint32_t)agg_dense_component_raw(c->plan, 0, (int64_t)p[begin + i]); \
gids[i] = slot; \
uint64_t bit = UINT64_C(1) << (slot % 64); \
_Atomic(uint64_t)* word = &c->occupied[slot / 64]; \
if (!(atomic_load_explicit(word, memory_order_relaxed) & bit)) \
atomic_fetch_or_explicit(word, bit, memory_order_relaxed); \
} \
} while (0)
switch (c->key->type) {
case RAY_I64: case RAY_TIMESTAMP: SHARED_GIDS(int64_t); break;
case RAY_I32: case RAY_DATE: case RAY_TIME: SHARED_GIDS(int32_t); break;
case RAY_I16: SHARED_GIDS(int16_t); break;
case RAY_U8: case RAY_BOOL: SHARED_GIDS(uint8_t); break;
case RAY_SYM:
switch (c->key->attrs & RAY_SYM_W_MASK) {
case RAY_SYM_W8: SHARED_GIDS(uint8_t); break;
case RAY_SYM_W16: SHARED_GIDS(uint16_t); break;
case RAY_SYM_W32: SHARED_GIDS(uint32_t); break;
default: SHARED_GIDS(int64_t); break;
}
break;
}
#undef SHARED_GIDS
for (uint32_t a = 0; a < c->n_aggs; a++) {
const void* vals = (const char*)d->val_data[a] + (size_t)begin * d->val_esz[a];
ray_valid_t valid = {vals, d->val_types[a], d->val_hasnull[a]};
vo->vts[a]->update_shared(c->states + vo->off[a], vo->block, gids, vals, &valid, n);
}
}
}
static void agg_dense_shared_occupied(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_dense_shared_ctx_t* c = raw;
for (int64_t s = start; s < end; s++)
c->first[s] = atomic_load_explicit(&c->occupied[s / 64], memory_order_relaxed)
& (UINT64_C(1) << (s % 64)) ? 0 : INT64_MAX;
}
static ray_t* agg_dense_shared(ray_t** key_cols, int64_t* key_syms, ray_op_ext_t* ext,
ray_pool_t* pool, const dense_plan_t* plan, int64_t rows,
const agg_vo_t* vo, const agg_desc_t* d, const ray_group_emit_filter_t* ef) {
int64_t slots = plan->total_slots;
agg_dense_shared_ctx_t c = {.key = key_cols[0], .plan = plan, .layout = vo, .desc = d, .n_aggs = ext->n_aggs};
c.states = ray_alloc_raw((size_t)slots * vo->block);
c.first = ray_alloc_raw((size_t)slots * sizeof(int64_t));
c.occupied = ray_alloc_raw(((size_t)slots + 63) / 64 * sizeof(*c.occupied));
if (!c.states || !c.first || !c.occupied) goto failed;
for (int64_t w = 0; w < (slots + 63) / 64; w++) atomic_init(&c.occupied[w], 0);
ray_pool_dispatch(pool, agg_dense_shared_init, &c, slots);
ray_profile_tick("dense shared: initialized states");
ray_pool_dispatch(pool, agg_dense_shared_rows, &c, rows);
if (agg_cancelled()) goto failed;
ray_profile_tick("dense shared: reduced rows");
ray_pool_dispatch(pool, agg_dense_shared_occupied, &c, slots);
if (agg_cancelled()) goto failed;
ray_profile_tick("dense shared: collected occupancy");
ray_free_raw(c.occupied);
route_stats.dense_strategy = AGG_DENSE_SHARED;
route_stats.dense_tasks = ray_pool_total_workers(pool);
route_stats.dense_local_slots = slots;
return agg_dense_finish(key_cols, key_syms, ext, pool, vo, d, slots, c.states, c.first, plan, slots, 0, ef, 0);
failed:
ray_free_raw(c.states); ray_free_raw(c.first); ray_free_raw(c.occupied);
return ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
static ray_t* exec_group_v2_parallel_dense(
ray_graph_t* g, ray_op_t* op, ray_t* tbl,
ray_t** key_cols, int64_t* key_syms, ray_op_ext_t* ext, int64_t nrows,
ray_pool_t* pool, const dense_plan_t* dp, uint32_t nw, agg_dense_strategy_t strategy,
ray_t* sel, const int64_t* sel_prefix, int64_t n_sel,
const ray_group_emit_filter_t* efp, int64_t group_limit) {
uint32_t n_keys = ext->n_keys, n_aggs = ext->n_aggs;
int64_t total_slots = dp->total_slots;
agg_vo_t vo;
if (!agg_vo_init(&vo, g, ext, tbl)) return ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
const agg_vtable_t** vts = vo.vts; size_t* off = vo.off; size_t block = vo.block;
agg_desc_t d;
if (!agg_desc_init(&d, g, ext, tbl, key_cols)) { agg_vo_free(&vo); return ray_error(agg_cancelled() ? "cancel" : "oom", NULL); }
const void** key_data = d.key_data;
const void** val_data = d.val_data; const int8_t* val_types = d.val_types;
const bool* val_hasnull = d.val_hasnull; const uint8_t* val_esz = d.val_esz;
const void** val2_data = d.val2_data; const int8_t* val2_types = d.val2_types;
const bool* val2_hasnull = d.val2_hasnull; const uint8_t* val2_esz = d.val2_esz;
if (strategy == AGG_DENSE_SHARED) {
ray_t* result = agg_dense_shared(key_cols, key_syms, ext, pool, dp, nrows, &vo, &d, efp);
agg_vo_free(&vo); agg_desc_free(&d);
return result;
}
if (strategy == AGG_DENSE_PARTITIONED) {
ray_t* result = agg_dense_partitioned(key_cols, key_syms, ext, pool, dp, sel ? n_sel : nrows, &vo, &d, sel, efp);
agg_vo_free(&vo); agg_desc_free(&d);
return result;
}
route_stats.dense_strategy = AGG_DENSE_TASK_LOCAL;
size_t metadata_bytes = ((size_t)nw * sizeof(agg_dense_local_t) + 63) & ~(size_t)63;
size_t local_bytes = metadata_bytes + 63;
for (uint32_t w = 0; w < nw; w++) {
size_t slots = total_slots;
size_t bytes = slots * (block + sizeof(int64_t)) + (slots + 63) / 64 * sizeof(uint64_t);
local_bytes += (bytes + 63) & ~(size_t)63;
}
agg_dense_local_t* locals = ray_alloc_raw(local_bytes);
if (!locals) { agg_vo_free(&vo); agg_desc_free(&d); return ray_error(agg_cancelled() ? "cancel" : "oom", NULL); }
memset(locals, 0, metadata_bytes);
char* cursor = (char*)(((uintptr_t)locals + metadata_bytes + 63) & ~(uintptr_t)63);
for (uint32_t w = 0; w < nw; w++) {
locals[w].slots = total_slots;
locals[w].eager = !sel && n_keys == 1 && total_slots <= nrows / nw / 2;
if (locals[w].eager && total_slots > 1) {
int64_t begin = nrows / nw * w;
int64_t end = w + 1 == nw ? nrows : nrows / nw * (w + 1);
int64_t low = total_slots, high = 0;
for (int64_t sample = 0; sample < 64; sample++) {
int64_t row = begin + (end - begin - 1) * sample / 63;
int64_t slot = agg_dense_component(dp, 0, agg_read_key_i64(key_cols[0], key_data[0], row));
if (slot < low) low = slot;
if (slot > high) high = slot;
}
locals[w].eager = high - low + 1 >= total_slots - total_slots / 4;
}
size_t slots = locals[w].slots;
size_t bytes = slots * (block + sizeof(int64_t)) + (slots + 63) / 64 * sizeof(uint64_t);
locals[w].states = cursor;
cursor += (bytes + 63) & ~(size_t)63;
}
agg_dense_merge_ctx_t merge_ctx = { .locals = locals, .slots = total_slots,
.vts = vts, .off = off, .block = block, .n_aggs = n_aggs, .nw = nw };
ray_profile_tick("dense: allocated slabs");
agg_dense_ctx_t ctx = {
.key_cols = key_cols, .key_data = key_data, .n_keys = n_keys, .dp = dp,
.vts = vts, .off = off, .block = block, .n_aggs = n_aggs,
.val_data = val_data, .val_types = val_types, .val_hasnull = val_hasnull, .val_esz = val_esz,
.val2_data = val2_data, .val2_types = val2_types, .val2_hasnull = val2_hasnull, .val2_esz = val2_esz,
.locals = locals,
.task_rows = sel ? n_sel : nrows, .n_tasks = nw,
.sel = sel, .sel_prefix = sel_prefix,
.vd = { .n_aggs = n_aggs, .vts = vts, .off = off, .block = block,
.val_data = val_data, .val_types = val_types, .val_hasnull = val_hasnull, .val_esz = val_esz,
.val2_data = val2_data, .val2_types = val2_types, .val2_hasnull = val2_hasnull, .val2_esz = val2_esz },
};
ray_pool_dispatch_n(pool, agg_dense_task_fn, &ctx, nw);
ray_profile_tick("dense: accumulated rows");
for (uint32_t w = 0; w < nw; w++)
if (locals[w].oom || agg_cancelled()) {
for (uint32_t i = 0; i < nw; i++)
agg_dense_local_destroy_states(&locals[i], vts, off, block, n_aggs);
ray_free_raw(locals);
agg_vo_free(&vo); agg_desc_free(&d);
return ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
char* gstates = ray_alloc_raw((size_t)total_slots * block);
int64_t* gfirst = ray_alloc_raw((size_t)total_slots * sizeof(int64_t));
if (!gstates || !gfirst) {
ray_free_raw(gstates); ray_free_raw(gfirst);
for (uint32_t i = 0; i < nw; i++)
agg_dense_local_destroy_states(&locals[i], vts, off, block, n_aggs);
ray_free_raw(locals);
agg_vo_free(&vo); agg_desc_free(&d);
return ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
merge_ctx.states = gstates; merge_ctx.first = gfirst;
ray_pool_dispatch(pool, agg_dense_merge_fn, &merge_ctx, total_slots);
ray_profile_tick("dense: merged slabs");
if (agg_cancelled()) {
for (uint32_t i = 0; i < nw; i++) {
agg_dense_local_destroy_states(&locals[i], vts, off, block, n_aggs);
}
ray_free_raw(locals); ray_free_raw(gstates); ray_free_raw(gfirst);
agg_vo_free(&vo); agg_desc_free(&d); return ray_error("cancel", NULL);
}
for (uint32_t i = 0; i < nw; i++)
agg_dense_local_destroy_states(&locals[i], vts, off, block, n_aggs);
ray_free_raw(locals);
ray_t* result = agg_dense_finish(key_cols, key_syms, ext, pool, &vo, &d,
total_slots, gstates, gfirst, NULL, 0, 0, efp, group_limit);
agg_vo_free(&vo); agg_desc_free(&d);
return result;
}
typedef struct {
int32_t* ht;
int64_t htcap;
uint64_t htmask;
int64_t* first_row;
char* states;
int64_t ng, cap;
size_t block;
int oom;
} agg_sh_t;
static int agg_sh_init(agg_sh_t* sh, int64_t cap, size_t block) {
if (cap < 1) cap = 1;
int64_t htcap = 2;
while (htcap < cap * 2) htcap <<= 1;
sh->ng = 0; sh->cap = cap; sh->block = block; sh->oom = 0;
sh->htcap = htcap; sh->htmask = (uint64_t)htcap - 1;
sh->ht = ray_alloc_raw((size_t)htcap * sizeof(int32_t));
sh->first_row = ray_alloc_raw((size_t)cap * sizeof(int64_t));
sh->states = ray_alloc_raw((size_t)cap * block);
if (!sh->ht || !sh->first_row || !sh->states) { sh->oom = 1; return -1; }
for (int64_t i = 0; i < htcap; i++) sh->ht[i] = -1;
return 0;
}
static void agg_sh_destroy(agg_sh_t* sh) {
ray_free_raw(sh->ht); ray_free_raw(sh->first_row); ray_free_raw(sh->states);
sh->ht = NULL; sh->first_row = NULL; sh->states = NULL;
}
static int agg_sh_grow_cap(agg_sh_t* sh) {
int64_t nc = sh->cap * 2;
int64_t* nfr = ray_realloc_raw(sh->first_row, (size_t)nc * sizeof(int64_t));
if (!nfr) { sh->oom = 1; return -1; }
sh->first_row = nfr;
char* nst = ray_realloc_raw(sh->states, (size_t)nc * sh->block);
if (!nst) { sh->oom = 1; return -1; }
sh->states = nst;
sh->cap = nc;
return 0;
}
static int agg_sh_grow_ht(agg_sh_t* sh, ray_t** key_cols, const void** key_data,
uint32_t n_keys) {
int64_t nc = sh->htcap * 2;
int32_t* nht = ray_alloc_raw((size_t)nc * sizeof(int32_t));
if (!nht) { sh->oom = 1; return -1; }
uint64_t nmask = (uint64_t)nc - 1;
for (int64_t i = 0; i < nc; i++) nht[i] = -1;
for (int64_t g = 0; g < sh->ng; g++) {
uint64_t h = agg_tuple_hash(key_cols, key_data, n_keys, sh->first_row[g]);
uint64_t slot = h & nmask;
while (nht[slot] >= 0) slot = (slot + 1) & nmask;
nht[slot] = (int32_t)g;
}
ray_free_raw(sh->ht);
sh->ht = nht; sh->htcap = nc; sh->htmask = nmask;
return 0;
}
#define AGG_SH_CHUNK 4096
static ray_t* exec_group_v2_parallel_radix(
ray_graph_t* g, ray_op_t* op, ray_t* tbl, int64_t nrows,
ray_t** key_cols, int64_t* key_syms,
const agg_vtable_t** vts, const size_t* off, size_t block,
ray_t* sel, const int64_t* sel_prefix, int64_t n_sel,
int64_t group_limit, const ray_group_emit_filter_t* efp);
typedef struct {
ray_t** key_cols;
const void** key_data;
uint32_t n_keys;
const agg_vtable_t** vts;
const size_t* off;
size_t block;
uint32_t n_aggs;
const void** val_data; const int8_t* val_types; const bool* val_hasnull; const uint8_t* val_esz;
const void** val2_data; const int8_t* val2_types; const bool* val2_hasnull; const uint8_t* val2_esz;
agg_sh_t* locals;
ray_t* sel;
const int64_t* sel_prefix;
agg_valdesc_t vd;
} agg_sh_ctx_t;
static inline int32_t agg_sh_find_or_insert(
agg_sh_t* sh, ray_t** key_cols, const void** key_data, uint32_t n_keys,
const agg_vtable_t** vts, const size_t* off, uint32_t n_aggs, int64_t r) {
uint64_t h = agg_tuple_hash(key_cols, key_data, n_keys, r);
uint64_t slot = h & sh->htmask;
for (;;) {
int32_t gp = sh->ht[slot];
if (gp < 0) {
if (sh->ng == sh->cap && agg_sh_grow_cap(sh) != 0) return -1;
if ((sh->ng + 1) * 2 > sh->htcap) {
if (agg_sh_grow_ht(sh, key_cols, key_data, n_keys) != 0) return -1;
slot = h & sh->htmask;
while (sh->ht[slot] >= 0) slot = (slot + 1) & sh->htmask;
}
int32_t g = (int32_t)sh->ng;
sh->ht[slot] = g;
sh->first_row[g] = r;
for (uint32_t a = 0; a < n_aggs; a++)
vts[a]->init(sh->states + (size_t)g * sh->block + off[a]);
sh->ng++;
return g;
}
if (agg_tuple_eq(key_cols, key_data, n_keys, r, sh->first_row[gp])) {
if (r < sh->first_row[gp]) sh->first_row[gp] = r;
return gp;
}
slot = (slot + 1) & sh->htmask;
}
}
static void agg_sh_phaseA_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
agg_sh_ctx_t* c = (agg_sh_ctx_t*)vctx;
agg_sh_t* sh = &c->locals[wid];
if (sh->oom) return;
uint32_t gid[AGG_SH_CHUNK];
if (c->sel) {
int64_t rows[AGG_SEL_CHUNK];
uint32_t sgid[AGG_SEL_CHUNK];
agg_sel_scratch_t sc;
if (!agg_sel_scratch_init(&sc, c->n_aggs)) { sh->oom = 1; return; }
agg_sel_cursor_t cur;
agg_sel_cursor_init(&cur, c->sel, c->sel_prefix, start, end);
int64_t cn;
while ((cn = agg_sel_cursor_next(&cur, rows)) > 0) {
for (int64_t i = 0; i < cn; i++) {
int32_t g = agg_sh_find_or_insert(sh, c->key_cols, c->key_data, c->n_keys,
c->vts, c->off, c->n_aggs, rows[i]);
if (g < 0) { sh->oom = 1; agg_sel_scratch_free(&sc); return; }
sgid[i] = (uint32_t)g;
}
if (agg_sel_accum_chunk(&c->vd, &sc, sh->states, sgid, rows, cn) != 0) {
sh->oom = 1; agg_sel_scratch_free(&sc); return;
}
}
agg_sel_scratch_free(&sc);
return;
}
for (int64_t cs = start; cs < end; cs += AGG_SH_CHUNK) {
int64_t ce = cs + AGG_SH_CHUNK; if (ce > end) ce = end;
int64_t n = ce - cs;
for (int64_t r = cs; r < ce; r++) {
int32_t g = agg_sh_find_or_insert(sh, c->key_cols, c->key_data, c->n_keys,
c->vts, c->off, c->n_aggs, r);
if (g < 0) { sh->oom = 1; return; }
gid[r - cs] = (uint32_t)g;
}
for (uint32_t a = 0; a < c->n_aggs; a++) {
if (c->vts[a]->update_batch2) {
const void* vx = (const char*)c->val_data[a] + (size_t)cs * c->val_esz[a];
const void* vy = (const char*)c->val2_data[a] + (size_t)cs * c->val2_esz[a];
ray_valid_t valid_x = { vx, c->val_types[a], c->val_hasnull[a] };
ray_valid_t valid_y = { vy, c->val2_types[a], c->val2_hasnull[a] };
c->vts[a]->update_batch2(sh->states + c->off[a], c->block, gid,
vx, vy, &valid_x, &valid_y, n, NULL);
} else {
const void* vals = (c->val_data[a])
? (const void*)((const char*)c->val_data[a] + (size_t)cs * c->val_esz[a])
: NULL;
ray_valid_t valid = { vals, c->val_types[a],
c->val_data[a] ? c->val_hasnull[a] : false };
c->vts[a]->update_batch(sh->states + c->off[a], c->block,
gid, vals, &valid, n, NULL);
}
}
}
}
static ray_t* exec_group_v2_parallel_smallhash(
ray_graph_t* g, ray_op_t* op, ray_t* tbl, int64_t nrows,
ray_t** key_cols, int64_t* key_syms,
const agg_vtable_t** vts, const size_t* off, size_t block,
ray_t* sel, const int64_t* sel_prefix, int64_t n_sel) {
agg_route_record(AGG_ROUTE_V2_SMALLHASH);
ray_op_ext_t* ext = find_ext(g, op->id);
uint32_t n_keys = ext->n_keys, n_aggs = ext->n_aggs;
ray_pool_t* pool = ray_pool_get();
uint32_t nw = ray_pool_total_workers(pool);
agg_desc_t d;
if (!agg_desc_init(&d, g, ext, tbl, key_cols)) return ray_error("oom", NULL);
const void** key_data = d.key_data;
const void** val_data = d.val_data; const int8_t* val_types = d.val_types;
const bool* val_hasnull = d.val_hasnull; const uint8_t* val_esz = d.val_esz;
const void** val2_data = d.val2_data; const int8_t* val2_types = d.val2_types;
const bool* val2_hasnull = d.val2_hasnull; const uint8_t* val2_esz = d.val2_esz;
const int64_t* agg_syms = d.agg_syms;
agg_sh_t* locals = ray_calloc_raw((size_t)((size_t)nw) * (sizeof(agg_sh_t)));
if (!locals) { agg_desc_free(&d); return ray_error("oom", NULL); }
int alloc_oom = 0;
for (uint32_t w = 0; w < nw; w++)
if (agg_sh_init(&locals[w], 1, block) != 0) { alloc_oom = 1; break; }
if (alloc_oom) {
for (uint32_t w = 0; w < nw; w++) agg_sh_destroy(&locals[w]);
ray_free_raw(locals);
agg_desc_free(&d);
return ray_error("oom", NULL);
}
agg_sh_ctx_t ctx = {
.key_cols = key_cols, .key_data = key_data, .n_keys = n_keys,
.vts = vts, .off = off, .block = block, .n_aggs = n_aggs,
.val_data = val_data, .val_types = val_types,
.val_hasnull = val_hasnull, .val_esz = val_esz,
.val2_data = val2_data, .val2_types = val2_types,
.val2_hasnull = val2_hasnull, .val2_esz = val2_esz,
.locals = locals,
.sel = sel, .sel_prefix = sel_prefix,
.vd = { .n_aggs = n_aggs, .vts = vts, .off = off, .block = block,
.val_data = val_data, .val_types = val_types, .val_hasnull = val_hasnull, .val_esz = val_esz,
.val2_data = val2_data, .val2_types = val2_types, .val2_hasnull = val2_hasnull, .val2_esz = val2_esz },
};
ray_pool_dispatch(pool, agg_sh_phaseA_fn, &ctx, sel ? n_sel : nrows);
for (uint32_t w = 0; w < nw; w++)
if (locals[w].oom) {
for (uint32_t i = 0; i < nw; i++) agg_sh_destroy(&locals[i]);
ray_free_raw(locals);
agg_desc_free(&d);
return ray_error("oom", NULL);
}
int64_t gcap = 0;
for (uint32_t w = 0; w < nw; w++) gcap += locals[w].ng;
agg_sh_t gt = {0};
if (agg_sh_init(>, gcap, block) != 0) {
agg_sh_destroy(>);
for (uint32_t i = 0; i < nw; i++) agg_sh_destroy(&locals[i]);
ray_free_raw(locals);
agg_desc_free(&d);
return ray_error("oom", NULL);
}
for (uint32_t w = 0; w < nw; w++) {
agg_sh_t* loc = &locals[w];
for (int64_t lg = 0; lg < loc->ng; lg++) {
int64_t fr = loc->first_row[lg];
int32_t gg = agg_sh_find_or_insert(>, key_cols, key_data, n_keys,
vts, off, n_aggs, fr);
if (gg < 0) {
agg_sh_destroy(>);
for (uint32_t i = 0; i < nw; i++) agg_sh_destroy(&locals[i]);
ray_free_raw(locals);
agg_desc_free(&d);
return ray_error("oom", NULL);
}
for (uint32_t a = 0; a < n_aggs; a++)
vts[a]->merge(gt.states + (size_t)gg * block + off[a],
loc->states + (size_t)lg * block + off[a], NULL);
}
}
int64_t ng = gt.ng;
for (uint32_t i = 0; i < nw; i++) agg_sh_destroy(&locals[i]);
ray_free_raw(locals);
ray_t* result = ray_table_new(n_keys + n_aggs);
if (!result || RAY_IS_ERR(result)) {
agg_sh_destroy(>); agg_desc_free(&d);
return result ? result : ray_error("oom", NULL);
}
for (uint32_t k = 0; k < n_keys; k++) {
ray_t* kc = ray_group_gather(key_cols[k], gt.first_row, ng);
if (!kc || RAY_IS_ERR(kc)) {
agg_sh_destroy(>); agg_desc_free(&d); ray_release(result);
return kc ? kc : ray_error("oom", NULL);
}
result = ray_table_add_col(result, key_syms[k], kc);
ray_release(kc);
}
for (uint32_t a = 0; a < n_aggs; a++) {
ray_t* out = ray_vec_new(vts[a]->out_type, ng);
if (!out || RAY_IS_ERR(out)) {
agg_sh_destroy(>); agg_desc_free(&d); ray_release(result);
return out ? out : ray_error("oom", NULL);
}
out->len = ng;
int64_t kparam = (ext->agg_k ? ext->agg_k[a] : 0);
for (int64_t i = 0; i < ng; i++) {
if (agg_finalize_value(vts[a], gt.states + (size_t)i * block + off[a], out, i, kparam))
out->attrs |= RAY_ATTR_HAS_NULLS;
}
int64_t agg_name = agg_result_col_name(agg_syms[a], ext->agg_ops[a]);
result = ray_table_add_col(result, agg_name, out);
ray_release(out);
}
agg_sh_destroy(>);
agg_desc_free(&d);
return result;
}
static uint32_t agg_radix_part_count(uint32_t nworkers, int64_t nrows) {
uint32_t n = 1;
uint64_t rows = nrows > 0 ? (uint64_t)nrows : 1;
while ((n < nworkers ||
(uint64_t)n < rows / n + (rows % n != 0)) &&
n <= UINT32_MAX / 2)
n <<= 1;
return n;
}
#define AGG_RADIX_CHUNK_MIN_ROWS 16384
#define AGG_RADIX_CHUNK_FLOOR_ROWS 4096
#define AGG_RADIX_CHUNKS_PER_WORKER 4
#define AGG_RADIX_COUNT_BUDGET ((size_t)64 << 20)
#define AGG_RADIX_PREFIX_BAND 64
typedef struct {
char* states;
int64_t* first_row;
int64_t* keys;
int64_t ng;
int oom;
} agg_radix_part_t;
static void agg_radix_parts_destroy(agg_radix_part_t* parts, uint32_t nparts,
const agg_vtable_t** vts, const size_t* off,
size_t block, uint32_t n_aggs) {
for (uint32_t p = 0; p < nparts; p++) {
char* states = parts[p].states;
if (states)
for (uint32_t a = 0; a < n_aggs; a++)
if (vts[a]->destroy)
for (int64_t gg = 0; gg < parts[p].ng; gg++)
vts[a]->destroy(states + (size_t)gg * block + off[a]);
ray_free_raw(parts[p].states);
ray_free_raw(parts[p].first_row);
ray_free_raw(parts[p].keys);
}
}
static ray_t* agg_unpack_key_col_new(ray_t* src_col, int64_t n) {
ray_t* out = col_vec_new(src_col, n);
if (!out || RAY_IS_ERR(out)) return out;
if (out->type == RAY_SYM)
ray_sym_vec_adopt_domain(out, sym_domain_rep(src_col));
out->len = n;
if (src_col->attrs & RAY_ATTR_HAS_NULLS) out->attrs |= RAY_ATTR_HAS_NULLS;
return out;
}
typedef struct {
ray_t* const* key_cols;
ray_t* const* outs;
const agg_radix_part_t* parts;
const agg_radix_order_t* pairs;
uint32_t n_keys;
} agg_key_emit_ctx_t;
RAY_INLINE void agg_key_emit_range(const agg_key_emit_ctx_t* c,
int64_t start, int64_t end) {
uint32_t n_keys = c->n_keys;
for (uint32_t k = 0; k < n_keys; k++) {
ray_t* out = c->outs[k];
void* dst = ray_data(out);
int8_t type = c->key_cols[k]->type;
uint8_t attrs = c->key_cols[k]->attrs;
for (int64_t i = start; i < end; i++) {
uint32_t p = (uint32_t)(c->pairs[i].idx >> 32);
uint32_t gg = (uint32_t)c->pairs[i].idx;
write_col_i64(dst, i,
c->parts[p].keys[(size_t)gg * n_keys + k], type, attrs);
}
}
}
static void agg_key_emit_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_key_emit_range((const agg_key_emit_ctx_t*)vctx, start, end);
}
typedef struct {
ray_t** key_cols;
const void** key_data;
uint32_t n_keys;
const agg_vtable_t** vts;
const size_t* off;
size_t block;
uint32_t n_aggs;
const void** val_data; const int8_t* val_types; const bool* val_hasnull; const uint8_t* val_esz;
const void** val2_data; const int8_t* val2_types; const bool* val2_hasnull; const uint8_t* val2_esz;
uint32_t nw;
uint32_t n_parts;
uint32_t part_bits;
int64_t n_in;
int64_t chunk_rows;
uint32_t n_chunks;
uint32_t* cnt;
int64_t* part_start;
char* pay;
uint16_t* pid;
agg_radix_part_t* parts;
_Atomic(int) part_overflow;
size_t rec;
const size_t* val_off;
const size_t* val2_off;
size_t row_off;
bool needs_row;
ray_t* sel;
const int64_t* sel_prefix;
} agg_radix_ctx_t;
static inline uint64_t agg_radix_fmix64(uint64_t h) {
h ^= h >> 33; h *= 0xff51afd7ed558ccdULL;
h ^= h >> 33; h *= 0xc4ceb9fe1a85ec53ULL;
h ^= h >> 33;
return h;
}
RAY_INLINE void agg_radix_visit(agg_radix_ctx_t* c, uint32_t* cur,
int64_t r, int64_t pos, bool scatter) {
uint32_t n_keys = c->n_keys, n_aggs = c->n_aggs;
if (!scatter) {
uint64_t h = 1469598103934665603ULL;
for (uint32_t k = 0; k < n_keys; k++) {
int64_t v = agg_read_key_i64(c->key_cols[k], c->key_data[k], r);
h ^= (uint64_t)v; h *= 1099511628211ULL;
}
h = agg_radix_fmix64(h);
uint32_t p = (uint32_t)(h & (c->n_parts - 1));
c->pid[pos] = (uint16_t)p;
cur[p]++;
return;
}
uint32_t p = c->pid[pos];
char* rec = c->pay + (size_t)(c->part_start[p] + cur[p]++) * c->rec;
int64_t* kdst = (int64_t*)rec;
for (uint32_t k = 0; k < n_keys; k++)
kdst[k] = agg_read_key_i64(c->key_cols[k], c->key_data[k], r);
int64_t input_order = pos;
for (uint32_t a = 0; a < n_aggs; a++) {
if (c->val_data[a]) {
uint8_t ez = c->val_esz[a];
memcpy(rec + c->val_off[a], (const char*)c->val_data[a] + (size_t)r * ez, ez);
}
if (c->val2_data[a]) {
uint8_t ez2 = c->val2_esz[a];
memcpy(rec + c->val2_off[a], (const char*)c->val2_data[a] + (size_t)r * ez2, ez2);
}
}
if (c->needs_row)
memcpy(rec + c->row_off, &input_order, sizeof(input_order));
}
RAY_INLINE void agg_radix_chunk_pass(agg_radix_ctx_t* c, int64_t ch, bool scatter) {
int64_t start = ch * c->chunk_rows;
int64_t end = start + c->chunk_rows;
if (end > c->n_in) end = c->n_in;
uint32_t* cur = c->cnt + (size_t)ch * c->n_parts;
if (!scatter) memset(cur, 0, (size_t)c->n_parts * sizeof(uint32_t));
if (c->sel) {
int64_t rows[AGG_SEL_CHUNK];
agg_sel_cursor_t sc;
agg_sel_cursor_init(&sc, c->sel, c->sel_prefix, start, end);
int64_t pos = start;
int64_t cn;
while ((cn = agg_sel_cursor_next(&sc, rows)) > 0)
for (int64_t i = 0; i < cn; i++)
agg_radix_visit(c, cur, rows[i], pos++, scatter);
return;
}
for (int64_t r = start; r < end; r++)
agg_radix_visit(c, cur, r, r, scatter);
}
static void agg_radix_count_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
for (int64_t ch = start; ch < end; ch++)
agg_radix_chunk_pass((agg_radix_ctx_t*)vctx, ch, false);
}
static void agg_radix_prefix_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_radix_ctx_t* c = (agg_radix_ctx_t*)vctx;
for (int64_t band = start; band < end; band++) {
uint32_t p0 = (uint32_t)band * AGG_RADIX_PREFIX_BAND;
uint32_t np = c->n_parts - p0;
if (np > AGG_RADIX_PREFIX_BAND) np = AGG_RADIX_PREFIX_BAND;
uint64_t run[AGG_RADIX_PREFIX_BAND] = {0};
for (uint32_t ch = 0; ch < c->n_chunks; ch++) {
uint32_t* row = c->cnt + (size_t)ch * c->n_parts + p0;
for (uint32_t j = 0; j < np; j++) {
uint32_t n = row[j];
row[j] = (uint32_t)run[j];
run[j] += n;
}
}
for (uint32_t j = 0; j < np; j++) {
if (run[j] > UINT32_MAX)
atomic_store_explicit(&c->part_overflow, 1, memory_order_relaxed);
c->part_start[p0 + j + 1] = (int64_t)run[j];
}
}
}
static void agg_radix_scatter_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
for (int64_t ch = start; ch < end; ch++)
agg_radix_chunk_pass((agg_radix_ctx_t*)vctx, ch, true);
}
static void agg_radix_group_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_radix_ctx_t* c = (agg_radix_ctx_t*)vctx;
uint32_t n_keys = c->n_keys, n_aggs = c->n_aggs;
size_t key_bytes = (size_t)n_keys * 8;
for (int64_t pi = start; pi < end; pi++) {
uint32_t p = (uint32_t)pi;
agg_radix_part_t* pr = &c->parts[p];
int64_t total = c->part_start[p + 1] - c->part_start[p];
if (total == 0) continue;
int64_t htcap = 16;
while (htcap < total * 2) htcap <<= 1;
uint64_t htmask = (uint64_t)htcap - 1;
int32_t* ht = ray_alloc_raw((size_t)htcap * sizeof(int32_t));
uint8_t* ht_salt = ray_alloc_raw((size_t)htcap);
int64_t gcap = total < 256 ? total : 256;
if (gcap < 16) gcap = 16;
int64_t* first_row = c->needs_row ? ray_alloc_raw((size_t)gcap * sizeof(int64_t)) : NULL;
char* states = ray_alloc_raw((size_t)gcap * c->block);
const int64_t** keyp = ray_alloc_raw((size_t)gcap * sizeof(int64_t*));
uint32_t* gid = ray_alloc_raw((size_t)total * sizeof(uint32_t));
int64_t* gkeys = ray_alloc_raw((size_t)gcap * (size_t)(n_keys ? n_keys : 1) * sizeof(int64_t));
if (!ht || !ht_salt || (c->needs_row && !first_row) || !states || !keyp || !gid || !gkeys) {
ray_free_raw(ht); ray_free_raw(ht_salt); ray_free_raw(first_row); ray_free_raw(states); ray_free_raw(keyp); ray_free_raw(gid); ray_free_raw(gkeys);
pr->oom = 1; return;
}
for (int64_t i = 0; i < htcap; i++) ht[i] = -1;
char** gv = ray_calloc_raw((size_t)(n_aggs ? n_aggs : 1) * sizeof(char*));
char** gy = ray_calloc_raw((size_t)(n_aggs ? n_aggs : 1) * sizeof(char*));
if (!gv || !gy) {
ray_free_raw(gv); ray_free_raw(gy);
ray_free_raw(ht); ray_free_raw(ht_salt); ray_free_raw(first_row); ray_free_raw(states); ray_free_raw(keyp); ray_free_raw(gid); ray_free_raw(gkeys);
pr->oom = 1; return;
}
for (uint32_t a = 0; a < n_aggs; a++) {
if (c->val_data[a]) {
uint8_t ez = c->val_esz[a];
gv[a] = ray_alloc_raw((size_t)total * (ez ? ez : 1));
if (!gv[a]) goto oom;
}
if (c->val2_data[a]) {
uint8_t ez2 = c->val2_esz[a];
gy[a] = ray_alloc_raw((size_t)total * (ez2 ? ez2 : 1));
if (!gy[a]) goto oom;
}
}
int64_t ng = 0, ri = 0;
{
const char* rec = c->pay + (size_t)c->part_start[p] * c->rec;
for (int64_t i = 0; i < total; i++, rec += c->rec) {
const int64_t* keys = (const int64_t*)rec;
int64_t r = 0;
if (c->needs_row) memcpy(&r, rec + c->row_off, sizeof(r));
for (uint32_t a = 0; a < n_aggs; a++) {
if (gv[a]) { uint8_t ez = c->val_esz[a];
memcpy(gv[a] + (size_t)ri * ez, rec + c->val_off[a], ez); }
if (gy[a]) { uint8_t ez2 = c->val2_esz[a];
memcpy(gy[a] + (size_t)ri * ez2, rec + c->val2_off[a], ez2); }
}
uint64_t h = 1469598103934665603ULL;
for (uint32_t k = 0; k < n_keys; k++) { h ^= (uint64_t)keys[k]; h *= 1099511628211ULL; }
h = agg_radix_fmix64(h);
uint64_t slot = (h >> c->part_bits) & htmask;
uint8_t salt = (uint8_t)(h >> 56);
int32_t gg;
for (;;) {
int32_t gp = ht[slot];
if (gp < 0) {
if ((int64_t)ng >= gcap) {
int64_t ncap = gcap * 2;
void* ns = ray_realloc_raw(states, (size_t)ncap * c->block);
if (!ns) goto oom;
states = ns;
void* nk = ray_realloc_raw((void*)keyp, (size_t)ncap * sizeof(int64_t*));
if (!nk) goto oom;
keyp = nk;
void* ngk = ray_realloc_raw(gkeys, (size_t)ncap * (size_t)(n_keys ? n_keys : 1) * sizeof(int64_t));
if (!ngk) goto oom;
gkeys = ngk;
if (c->needs_row) {
void* nf = ray_realloc_raw(first_row, (size_t)ncap * sizeof(int64_t));
if (!nf) goto oom;
first_row = nf;
}
gcap = ncap;
}
gg = (int32_t)ng;
ht[slot] = gg;
ht_salt[slot] = salt;
if (c->needs_row) first_row[gg] = r;
keyp[gg] = keys;
for (uint32_t k = 0; k < n_keys; k++)
gkeys[(size_t)gg * n_keys + k] = keys[k];
for (uint32_t a = 0; a < n_aggs; a++)
c->vts[a]->init(states + (size_t)gg * c->block + c->off[a]);
ng++;
break;
}
if (ht_salt[slot] == salt && memcmp(keys, keyp[gp], key_bytes) == 0) {
gg = gp;
if (c->needs_row && r < first_row[gg]) first_row[gg] = r;
break;
}
slot = (slot + 1) & htmask;
}
gid[ri] = (uint32_t)gg;
ri++;
}
}
ray_free_raw(ht); ray_free_raw(ht_salt); ray_free_raw(keyp);
for (uint32_t a = 0; a < n_aggs; a++) {
if (c->vts[a]->update_batch2) {
ray_valid_t vx = { gv[a], c->val_types[a], c->val_hasnull[a] };
ray_valid_t vy = { gy[a], c->val2_types[a], c->val2_hasnull[a] };
c->vts[a]->update_batch2(states + c->off[a], c->block, gid,
gv[a], gy[a], &vx, &vy, total, NULL);
} else if (c->val_data[a]) {
ray_valid_t valid = { gv[a], c->val_types[a], c->val_hasnull[a] };
c->vts[a]->update_batch(states + c->off[a], c->block, gid, gv[a], &valid, total, NULL);
} else {
ray_valid_t valid = { NULL, c->val_types[a], false };
c->vts[a]->update_batch(states + c->off[a], c->block, gid, NULL, &valid, total, NULL);
}
}
for (uint32_t a = 0; a < n_aggs; a++) { ray_free_raw(gv[a]); ray_free_raw(gy[a]); }
ray_free_raw(gv); ray_free_raw(gy);
ray_free_raw(gid);
pr->states = states; pr->first_row = first_row; pr->keys = gkeys; pr->ng = ng;
continue;
oom:
ray_free_raw(ht); ray_free_raw(ht_salt); ray_free_raw(first_row); ray_free_raw(states); ray_free_raw(keyp); ray_free_raw(gid); ray_free_raw(gkeys);
for (uint32_t a = 0; a < n_aggs; a++) { ray_free_raw(gv[a]); ray_free_raw(gy[a]); }
ray_free_raw(gv); ray_free_raw(gy);
pr->oom = 1; return;
}
}
typedef struct {
agg_radix_part_t* parts;
agg_radix_order_t* pairs;
int64_t input_count;
_Atomic(int) fail;
} agg_ord_scatter_ctx_t;
static void agg_ord_scatter_fn(void* vctx, uint32_t wid, int64_t start,
int64_t end) {
(void)wid;
agg_ord_scatter_ctx_t* c = (agg_ord_scatter_ctx_t*)vctx;
if (atomic_load_explicit(&c->fail, memory_order_relaxed)) return;
for (int64_t p = start; p < end; p++) {
agg_radix_part_t* pr = &c->parts[p];
for (int64_t gg = 0; gg < pr->ng; gg++) {
int64_t first = pr->first_row[gg];
if (first < 0 || first >= c->input_count) {
atomic_store_explicit(&c->fail, 1, memory_order_relaxed);
return;
}
c->pairs[first].idx = ((int64_t)p << 32) | (uint32_t)gg;
}
}
}
typedef struct {
agg_radix_order_t* pairs;
agg_radix_order_t* dst;
int64_t input_count;
int64_t chunk;
int64_t* counts;
} agg_ord_compact_ctx_t;
static void agg_ord_count_fn(void* vctx, uint32_t wid, int64_t start,
int64_t end) {
(void)wid;
agg_ord_compact_ctx_t* c = (agg_ord_compact_ctx_t*)vctx;
for (int64_t ch = start; ch < end; ch++) {
int64_t lo = ch * c->chunk;
int64_t hi = lo + c->chunk;
if (hi > c->input_count) hi = c->input_count;
int64_t n = 0;
for (int64_t i = lo; i < hi; i++)
n += (c->pairs[i].idx != -1);
c->counts[ch] = n;
}
}
static void agg_ord_compact_fn(void* vctx, uint32_t wid, int64_t start,
int64_t end) {
(void)wid;
agg_ord_compact_ctx_t* c = (agg_ord_compact_ctx_t*)vctx;
for (int64_t ch = start; ch < end; ch++) {
int64_t lo = ch * c->chunk;
int64_t hi = lo + c->chunk;
if (hi > c->input_count) hi = c->input_count;
int64_t w = c->counts[ch];
for (int64_t i = lo; i < hi; i++)
if (c->pairs[i].idx != -1) c->dst[w++].idx = c->pairs[i].idx;
}
}
typedef struct {
agg_radix_part_t* parts;
const agg_radix_order_t* pairs;
const agg_vtable_t** vts;
const size_t* off;
size_t block;
uint32_t n_aggs;
const int64_t* agg_k;
ray_t** outs;
uint8_t* saw_null;
} agg_radix_fin_ctx_t;
static inline bool agg_put_cell_value(ray_t* out, int64_t i, ray_t* cell) {
bool is_null = RAY_ATOM_IS_NULL(cell);
switch (out->type) {
case RAY_BOOL: case RAY_U8: ((uint8_t*)ray_data(out))[i] = cell->u8; break;
case RAY_I16: ((int16_t*)ray_data(out))[i] = is_null ? NULL_I16 : cell->i16; break;
case RAY_I32: case RAY_DATE: case RAY_TIME:
((int32_t*)ray_data(out))[i] = is_null ? NULL_I32 : cell->i32; break;
case RAY_F32: ((float*)ray_data(out))[i] = is_null ? NULL_F32 : (float)cell->f64; break;
case RAY_F64: ((double*)ray_data(out))[i] = is_null ? NULL_F64 : cell->f64; break;
default: ((int64_t*)ray_data(out))[i] = is_null ? NULL_I64 : cell->i64; break;
}
return is_null;
}
static void agg_radix_finalize_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
agg_radix_fin_ctx_t* c = (agg_radix_fin_ctx_t*)vctx;
uint32_t n_aggs = c->n_aggs;
for (uint32_t a = 0; a < n_aggs; a++) {
ray_t* out = c->outs[a];
if (!out) continue;
int64_t kparam = c->agg_k ? c->agg_k[a] : 0;
bool any_null = false;
for (int64_t i = start; i < end; i++) {
uint32_t p = (uint32_t)(c->pairs[i].idx >> 32);
uint32_t gg = (uint32_t)(c->pairs[i].idx & 0xffffffffu);
const void* state = c->parts[p].states + (size_t)gg * c->block + c->off[a];
any_null |= agg_finalize_value(c->vts[a], state, out, i, kparam);
}
if (any_null) c->saw_null[(size_t)wid * n_aggs + a] = 1;
}
}
static void agg_sort_pairs_by_key(agg_radix_order_t* pairs, int64_t* keys, int64_t n) {
#define PAIRS_SIFT_DOWN(START, END) do { \
int64_t i_ = (START); \
for (;;) { \
int64_t l = 2 * i_ + 1, r = l + 1, m = i_; \
if (l < (END) && keys[l] > keys[m]) m = l; \
if (r < (END) && keys[r] > keys[m]) m = r; \
if (m == i_) break; \
int64_t tk = keys[m]; keys[m] = keys[i_]; keys[i_] = tk; \
int64_t tp = pairs[m].idx; pairs[m].idx = pairs[i_].idx; pairs[i_].idx = tp; \
i_ = m; \
} \
} while (0)
for (int64_t start = n / 2 - 1; start >= 0; start--) PAIRS_SIFT_DOWN(start, n);
for (int64_t end = n - 1; end > 0; end--) {
int64_t tk = keys[0]; keys[0] = keys[end]; keys[end] = tk;
int64_t tp = pairs[0].idx; pairs[0].idx = pairs[end].idx; pairs[end].idx = tp;
PAIRS_SIFT_DOWN(0, end);
}
#undef PAIRS_SIFT_DOWN
}
typedef struct {
const agg_radix_part_t* parts;
const agg_vtable_t* vt;
size_t off, block;
int64_t param;
const ray_group_emit_filter_t* ef;
double* vals;
uint8_t* keep;
const int64_t* base;
double* cand;
int64_t* cand_n;
int64_t cap;
bool have_thr;
double thr;
int64_t* kept;
_Atomic(int) fail;
} agg_radix_vals_ctx_t;
static void agg_radix_vals_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_radix_vals_ctx_t* c = raw;
for (int64_t p = start; p < end; p++) {
double* v = c->vals + c->base[p];
if (!agg_group_values_f64(c->vt, c->parts[p].states, c->block, c->off, NULL,
c->parts[p].ng, c->param, v)) {
atomic_store_explicit(&c->fail, 1, memory_order_relaxed);
continue;
}
if (c->cap > 0)
agg_topn_candidates(v, c->parts[p].ng, c->cap, c->ef->desc,
c->cand + (size_t)p * c->cap, &c->cand_n[p]);
}
}
static void agg_radix_mark_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_radix_vals_ctx_t* c = raw;
for (int64_t p = start; p < end; p++)
c->kept[p] = agg_topn_mark(c->vals + c->base[p], c->parts[p].ng, c->ef,
c->have_thr, c->thr, c->keep + c->base[p]);
}
static agg_radix_order_t* __attribute__((noinline))
agg_radix_select_topn(ray_pool_t* pool, const agg_radix_part_t* parts, uint32_t n_parts,
const agg_vtable_t* vt, size_t off, size_t block, int64_t param,
const ray_group_emit_filter_t* ef, int64_t* n_emit, int* rc) {
*rc = 0;
int64_t ng = 0;
int64_t* base = ray_alloc_raw(((size_t)n_parts + 1) * sizeof(int64_t));
if (!base) { *rc = 1; return NULL; }
for (uint32_t p = 0; p < n_parts; p++) { base[p] = ng; ng += parts[p].ng; }
base[n_parts] = ng;
double* vals = ray_alloc_raw((size_t)(ng > 0 ? ng : 1) * sizeof(double));
uint8_t* keep = ray_alloc_raw((size_t)(ng > 0 ? ng : 1));
if (!vals || !keep) {
ray_free_raw(base); ray_free_raw(vals); ray_free_raw(keep); *rc = 1; return NULL;
}
int64_t cap = ef->top_count_take > 0 && ng > ef->top_count_take ? ef->top_count_take : 0;
double* cand = ray_alloc_raw((size_t)(cap > 0 ? cap * n_parts : 1) * sizeof(double));
int64_t* cand_n = ray_calloc_raw((size_t)n_parts * 2 * sizeof(int64_t));
if (!cand || !cand_n) {
ray_free_raw(base); ray_free_raw(vals); ray_free_raw(keep);
ray_free_raw(cand); ray_free_raw(cand_n); *rc = 1; return NULL;
}
agg_radix_vals_ctx_t c = { .parts = parts, .vt = vt, .off = off, .block = block,
.param = param, .ef = ef, .vals = vals, .keep = keep, .base = base,
.cand = cand, .cand_n = cand_n, .cap = cap, .kept = cand_n + n_parts };
atomic_init(&c.fail, 0);
if (pool) ray_pool_dispatch_n(pool, agg_radix_vals_fn, &c, n_parts);
else agg_radix_vals_fn(&c, 0, 0, n_parts);
if (atomic_load_explicit(&c.fail, memory_order_relaxed)) {
ray_free_raw(base); ray_free_raw(vals); ray_free_raw(keep);
ray_free_raw(cand); ray_free_raw(cand_n); *rc = 2; return NULL;
}
if (cap > 0) {
int64_t nc = 0;
for (uint32_t p = 0; p < n_parts; p++) {
memmove(cand + nc, cand + (size_t)p * cap, (size_t)cand_n[p] * sizeof(double));
nc += cand_n[p];
}
c.have_thr = agg_topn_union_threshold(cand, nc, ng, ef, &c.thr);
}
if (pool) ray_pool_dispatch_n(pool, agg_radix_mark_fn, &c, n_parts);
else agg_radix_mark_fn(&c, 0, 0, n_parts);
int64_t kept = 0;
for (uint32_t p = 0; p < n_parts; p++) kept += c.kept[p];
ray_free_raw(cand); ray_free_raw(cand_n);
int64_t take = c.have_thr ? ef->top_count_take : 0;
int64_t sel_cap = c.have_thr ? 2 * take : kept;
if (sel_cap <= 0) sel_cap = 1;
agg_radix_order_t* sel = ray_alloc_raw((size_t)sel_cap * sizeof(agg_radix_order_t));
int64_t* fr = ray_alloc_raw((size_t)sel_cap * sizeof(int64_t));
if (!sel || !fr) {
ray_free_raw(sel); ray_free_raw(fr);
ray_free_raw(base); ray_free_raw(vals); ray_free_raw(keep); *rc = 1; return NULL;
}
int64_t k = 0;
if (!c.have_thr) {
for (uint32_t p = 0; p < n_parts; p++)
for (int64_t gg = 0; gg < parts[p].ng; gg++)
if (keep[base[p] + gg]) {
sel[k].idx = ((int64_t)p << 32) | (uint32_t)gg;
fr[k] = parts[p].first_row[gg];
k++;
}
} else {
agg_radix_order_t* hp = sel + take;
int64_t* hk = fr + take;
int64_t hn = 0;
bool overflow = false;
for (uint32_t p = 0; p < n_parts && !overflow; p++) {
const double* pv = vals + base[p];
const uint8_t* pk = keep + base[p];
const int64_t* pfr = parts[p].first_row;
for (int64_t gg = 0; gg < parts[p].ng; gg++) {
if (!pk[gg]) continue;
int64_t payload = ((int64_t)p << 32) | (uint32_t)gg;
if (pv[gg] != c.thr) {
if (k >= take) { overflow = true; break; }
sel[k].idx = payload; fr[k] = pfr[gg]; k++;
continue;
}
int64_t first = pfr[gg];
if (hn < take) {
int64_t i = hn++;
hk[i] = first; hp[i].idx = payload;
while (i > 0) {
int64_t par = (i - 1) / 2;
if (hk[par] >= hk[i]) break;
int64_t tk = hk[par]; hk[par] = hk[i]; hk[i] = tk;
int64_t tp = hp[par].idx; hp[par].idx = hp[i].idx; hp[i].idx = tp;
i = par;
}
} else if (first < hk[0]) {
hk[0] = first; hp[0].idx = payload;
int64_t i = 0;
for (;;) {
int64_t l = 2 * i + 1, r = l + 1, m = i;
if (l < take && hk[l] > hk[m]) m = l;
if (r < take && hk[r] > hk[m]) m = r;
if (m == i) break;
int64_t tk = hk[m]; hk[m] = hk[i]; hk[i] = tk;
int64_t tp = hp[m].idx; hp[m].idx = hp[i].idx; hp[i].idx = tp;
i = m;
}
}
}
}
if (overflow) {
ray_free_raw(sel); ray_free_raw(fr);
ray_free_raw(base); ray_free_raw(vals); ray_free_raw(keep); *rc = 2; return NULL;
}
if (k < take) {
memmove(sel + k, hp, (size_t)hn * sizeof(agg_radix_order_t));
memmove(fr + k, hk, (size_t)hn * sizeof(int64_t));
}
k += hn;
}
agg_sort_pairs_by_key(sel, fr, k);
ray_free_raw(fr); ray_free_raw(base); ray_free_raw(vals); ray_free_raw(keep);
*n_emit = k;
return sel;
}
static agg_radix_order_t* __attribute__((noinline))
agg_radix_select_first_n(const agg_radix_part_t* parts, uint32_t n_parts,
int64_t input_count, int64_t n_emit, int* rc) {
*rc = 0;
agg_radix_order_t* sel_pairs =
ray_alloc_raw((size_t)n_emit * sizeof(agg_radix_order_t));
ray_t* hk_hdr = NULL;
int64_t* hkey = sel_pairs
? (int64_t*)scratch_alloc(&hk_hdr, (size_t)n_emit * sizeof(int64_t))
: NULL;
if (!sel_pairs || !hkey) {
ray_free_raw(sel_pairs); scratch_free(hk_hdr);
*rc = 1; return NULL;
}
int64_t hn = 0;
bool ok = true;
for (uint32_t p = 0; p < n_parts && ok; p++) {
const int64_t* fr = parts[p].first_row;
for (int64_t gg = 0; gg < parts[p].ng; gg++) {
int64_t first = fr[gg];
if (first < 0 || first >= input_count) { ok = false; break; }
int64_t payload = ((int64_t)p << 32) | (uint32_t)gg;
if (hn < n_emit) {
int64_t i = hn++;
hkey[i] = first; sel_pairs[i].idx = payload;
while (i > 0) {
int64_t par = (i - 1) / 2;
if (hkey[par] >= hkey[i]) break;
int64_t tk = hkey[par]; hkey[par] = hkey[i]; hkey[i] = tk;
int64_t tp = sel_pairs[par].idx;
sel_pairs[par].idx = sel_pairs[i].idx; sel_pairs[i].idx = tp;
i = par;
}
} else if (first < hkey[0]) {
hkey[0] = first; sel_pairs[0].idx = payload;
int64_t i = 0;
for (;;) {
int64_t l = 2 * i + 1, r = l + 1, m = i;
if (l < n_emit && hkey[l] > hkey[m]) m = l;
if (r < n_emit && hkey[r] > hkey[m]) m = r;
if (m == i) break;
int64_t tk = hkey[m]; hkey[m] = hkey[i]; hkey[i] = tk;
int64_t tp = sel_pairs[m].idx;
sel_pairs[m].idx = sel_pairs[i].idx; sel_pairs[i].idx = tp;
i = m;
}
}
}
}
if (ok) {
agg_sort_pairs_by_key(sel_pairs, hkey, hn);
ok = hn == n_emit;
}
scratch_free(hk_hdr);
if (!ok) { ray_free_raw(sel_pairs); *rc = 2; return NULL; }
return sel_pairs;
}
typedef struct { agg_radix_order_t* pairs; } agg_ord_fill_ctx_t;
static void agg_ord_fill_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_ord_fill_ctx_t* c = raw;
memset(c->pairs + start, 0xFF, (size_t)(end - start) * sizeof(agg_radix_order_t));
}
#define AGG_ORDER_SORT_MAX_GROUPS 16384
static ray_t* agg_parts_emit(ray_op_ext_t* ext, agg_desc_t* d,
agg_radix_part_t* parts, uint32_t n_parts, ray_pool_t* pool, uint32_t nw,
ray_t** key_cols, int64_t* key_syms,
const agg_vtable_t** vts, const size_t* off, size_t block,
int64_t input_count, int64_t group_limit, const ray_group_emit_filter_t* efp) {
uint32_t n_keys = ext->n_keys, n_aggs = ext->n_aggs;
const int64_t* agg_syms = d->agg_syms;
int64_t ng = 0;
for (uint32_t p = 0; p < n_parts; p++) ng += parts[p].ng;
int64_t n_emit = ng;
agg_radix_order_t* pairs = NULL;
if (efp && group_limit == 0) {
int rc = 0;
int64_t kept = 0;
agg_radix_order_t* sel_pairs = agg_radix_select_topn(pool, parts, n_parts,
vts[efp->agg_index], off[efp->agg_index], block,
ext->agg_k ? ext->agg_k[efp->agg_index] : 0, efp, &kept, &rc);
if (sel_pairs) {
pairs = sel_pairs;
n_emit = kept;
route_stats.topn_native = true;
route_stats.topn_kept = kept;
} else if (rc == 1) {
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(d);
return ray_error("oom", NULL);
}
}
if (pairs) {
} else if (group_limit > 0 && ng > group_limit) {
int rc = 0;
n_emit = group_limit;
agg_radix_order_t* sel_pairs = agg_radix_select_first_n(
parts, n_parts, input_count, n_emit, &rc);
if (!sel_pairs) {
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(d);
return rc == 1 ? ray_error("oom", NULL)
: ray_error("group", "failed to order radix groups");
}
pairs = sel_pairs;
} else if (ng <= AGG_ORDER_SORT_MAX_GROUPS && ng * 32 <= input_count) {
pairs = ray_alloc_raw((size_t)(ng > 0 ? ng : 1) * sizeof(agg_radix_order_t));
ray_t* sk_hdr = NULL;
int64_t* skeys = pairs ? (int64_t*)scratch_alloc(&sk_hdr,
(size_t)(ng > 0 ? ng : 1) * sizeof(int64_t)) : NULL;
if (!pairs || !skeys) {
ray_free_raw(pairs); scratch_free(sk_hdr);
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(d);
return ray_error("oom", NULL);
}
int64_t m = 0;
for (uint32_t p = 0; p < n_parts; p++)
for (int64_t gg = 0; gg < parts[p].ng; gg++, m++) {
skeys[m] = parts[p].first_row[gg];
pairs[m].idx = ((int64_t)p << 32) | (uint32_t)gg;
}
agg_sort_pairs_by_key(pairs, skeys, ng);
scratch_free(sk_hdr);
} else {
pairs = ray_alloc_raw(
(size_t)(input_count > 0 ? input_count : 1) * sizeof(agg_radix_order_t));
if (!pairs) {
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(d);
return ray_error("oom", NULL);
}
{
agg_ord_fill_ctx_t fctx = { .pairs = pairs };
if (pool && input_count >= RAY_PARALLEL_THRESHOLD)
ray_pool_dispatch(pool, agg_ord_fill_fn, &fctx, input_count);
else
memset(pairs, 0xFF, (size_t)(input_count > 0 ? input_count : 1) * sizeof(agg_radix_order_t));
}
bool order_ok = true;
int64_t ordered = 0;
bool ord_parallel_done = false;
const int64_t ORD_CHUNK = 1 << 17;
const int64_t ORD_MAX_CHUNKS = RAY_POOL_MAX_TASKS / 4;
if (pool && nw > 1 && input_count >= (1 << 20) &&
(input_count + ORD_CHUNK - 1) / ORD_CHUNK <= ORD_MAX_CHUNKS) {
int64_t n_chunks = (input_count + ORD_CHUNK - 1) / ORD_CHUNK;
ray_t* ordcnt_hdr = NULL;
int64_t* ord_counts = (int64_t*)scratch_alloc(&ordcnt_hdr,
(size_t)n_chunks * sizeof(int64_t));
agg_radix_order_t* compacted = ray_alloc_raw((size_t)(ng > 0 ? ng : 1) * sizeof(agg_radix_order_t));
if (ord_counts && compacted) {
agg_ord_scatter_ctx_t sctx = {
.parts = parts, .pairs = pairs,
.input_count = input_count, .fail = 0,
};
ray_pool_dispatch_n(pool, agg_ord_scatter_fn, &sctx, (uint32_t)n_parts);
if (!atomic_load_explicit(&sctx.fail, memory_order_relaxed)) {
agg_ord_compact_ctx_t cctx = {
.pairs = pairs, .dst = compacted, .input_count = input_count,
.chunk = ORD_CHUNK, .counts = ord_counts,
};
ray_pool_dispatch_n(pool, agg_ord_count_fn, &cctx, (uint32_t)n_chunks);
int64_t run = 0;
for (int64_t ch = 0; ch < n_chunks; ch++) {
int64_t n = ord_counts[ch];
ord_counts[ch] = run;
run += n;
}
ordered = run;
order_ok = ordered == ng;
if (order_ok) {
ray_pool_dispatch_n(pool, agg_ord_compact_fn, &cctx, (uint32_t)n_chunks);
ray_free_raw(pairs);
pairs = compacted;
compacted = NULL;
}
ord_parallel_done = true;
} else {
order_ok = false;
ord_parallel_done = true;
}
}
ray_free_raw(compacted);
scratch_free(ordcnt_hdr);
}
if (!ord_parallel_done) {
for (uint32_t p = 0; p < n_parts && order_ok; p++) {
for (int64_t gg = 0; gg < parts[p].ng; gg++) {
int64_t first = parts[p].first_row[gg];
if (first < 0 || first >= input_count || pairs[first].idx != -1) {
order_ok = false;
break;
}
pairs[first].idx = ((int64_t)p << 32) | (uint32_t)gg;
}
}
if (order_ok) {
for (int64_t i = 0; i < input_count; i++)
if (pairs[i].idx != -1) pairs[ordered++].idx = pairs[i].idx;
order_ok = ordered == ng;
}
}
if (!order_ok) {
ray_free_raw(pairs);
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(d);
return ray_error("group", "failed to order radix groups");
}
}
ray_profile_tick("radix: ordered groups");
ray_t* result = ray_table_new(n_keys + n_aggs);
if (!result || RAY_IS_ERR(result)) {
ray_free_raw(pairs);
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(d);
return result ? result : ray_error("oom", NULL);
}
ray_t* kouts_hdr = NULL;
ray_t** kouts = (ray_t**)scratch_calloc(&kouts_hdr, (size_t)n_keys * sizeof(ray_t*));
ray_t* kerr = kouts ? NULL : ray_error("oom", NULL);
uint32_t k_built = 0;
for (; !kerr && k_built < n_keys; k_built++) {
ray_t* kc = agg_unpack_key_col_new(key_cols[k_built], n_emit);
if (!kc || RAY_IS_ERR(kc)) { kerr = kc ? kc : ray_error("oom", NULL); break; }
kouts[k_built] = kc;
}
if (!kerr) {
agg_key_emit_ctx_t kctx = {
.key_cols = key_cols, .outs = kouts, .parts = parts,
.pairs = pairs, .n_keys = n_keys,
};
if (ray_pool_par_dispatch_ok(pool, n_emit, RAY_PARALLEL_THRESHOLD)) {
ray_pool_dispatch(pool, agg_key_emit_fn, &kctx, n_emit);
if (pool_cancelled(pool)) kerr = ray_error("cancel", NULL);
} else {
agg_key_emit_range(&kctx, 0, n_emit);
}
}
if (kerr) {
for (uint32_t k = 0; k < k_built; k++) ray_release(kouts[k]);
scratch_free(kouts_hdr);
ray_free_raw(pairs);
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(d);
ray_release(result); return kerr;
}
for (uint32_t k = 0; k < n_keys; k++) {
result = ray_table_add_col(result, key_syms[k], kouts[k]);
ray_release(kouts[k]);
}
scratch_free(kouts_hdr);
ray_t* outs_hdr;
ray_t** outs = (ray_t**)scratch_calloc(&outs_hdr, (size_t)n_aggs * (sizeof(ray_t*) + sizeof(int64_t)));
if (!outs) {
ray_free_raw(pairs);
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(d);
ray_release(result); return ray_error("oom", NULL);
}
int64_t* kparams = (int64_t*)(outs + n_aggs);
for (uint32_t a = 0; a < n_aggs; a++) {
bool is_list = (vts[a]->out_type == RAY_LIST);
ray_t* out = is_list ? ray_list_new(n_emit)
: ray_vec_new(vts[a]->out_type, n_emit);
if (!out || RAY_IS_ERR(out)) {
for (uint32_t b = 0; b < a; b++) ray_release(outs[b]);
ray_free_raw(pairs);
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
scratch_free(outs_hdr); agg_desc_free(d);
ray_release(result); return out ? out : ray_error("oom", NULL);
}
out->len = n_emit;
outs[a] = out;
kparams[a] = (ext->agg_k ? ext->agg_k[a] : 0);
}
bool par_fin = (pool && n_emit >= RAY_PARALLEL_THRESHOLD);
if (par_fin) {
uint8_t* saw_null = ray_calloc_raw((size_t)((size_t)nw * (n_aggs ? n_aggs : 1)) * (1));
ray_t* scalar_hdr = NULL;
ray_t** scalar_outs = saw_null
? (ray_t**)scratch_alloc(&scalar_hdr, (size_t)n_aggs * sizeof(ray_t*)) : NULL;
if (!saw_null || !scalar_outs) { par_fin = false; ray_free_raw(saw_null); scratch_free(scalar_hdr); }
else {
for (uint32_t a = 0; a < n_aggs; a++)
scalar_outs[a] = (vts[a]->out_type == RAY_LIST) ? NULL : outs[a];
agg_radix_fin_ctx_t fc = {
.parts = parts, .pairs = pairs, .vts = vts, .off = off,
.block = block, .n_aggs = n_aggs, .agg_k = kparams,
.outs = scalar_outs, .saw_null = saw_null,
};
ray_pool_dispatch(pool, agg_radix_finalize_fn, &fc, n_emit);
for (uint32_t a = 0; a < n_aggs; a++) {
if (vts[a]->out_type == RAY_LIST) continue;
for (uint32_t w = 0; w < nw; w++)
if (saw_null[(size_t)w * n_aggs + a]) {
outs[a]->attrs |= RAY_ATTR_HAS_NULLS;
break;
}
}
ray_free_raw(saw_null);
scratch_free(scalar_hdr);
}
}
for (uint32_t a = 0; a < n_aggs; a++) {
bool is_list = (vts[a]->out_type == RAY_LIST);
ray_t* out = outs[a];
if (is_list || !par_fin) {
for (int64_t i = 0; i < n_emit; i++) {
uint32_t p = (uint32_t)(pairs[i].idx >> 32);
uint32_t gg = (uint32_t)(pairs[i].idx & 0xffffffffu);
const void* state = parts[p].states + (size_t)gg * block + off[a];
if (is_list) {
ray_t* cell = vts[a]->finalize(state, NULL, kparams[a]);
out = ray_list_set(out, i, cell);
ray_release(cell);
} else if (agg_finalize_value(vts[a], state, out, i, kparams[a])) {
out->attrs |= RAY_ATTR_HAS_NULLS;
}
}
outs[a] = out;
}
int64_t agg_name = agg_result_col_name(agg_syms[a], ext->agg_ops[a]);
result = ray_table_add_col(result, agg_name, out);
ray_release(out);
}
ray_free_raw(pairs);
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
scratch_free(outs_hdr); agg_desc_free(d);
return result;
}
static ray_t* exec_group_v2_parallel_radix(
ray_graph_t* g, ray_op_t* op, ray_t* tbl, int64_t nrows,
ray_t** key_cols, int64_t* key_syms,
const agg_vtable_t** vts, const size_t* off, size_t block,
ray_t* sel, const int64_t* sel_prefix, int64_t n_sel,
int64_t group_limit, const ray_group_emit_filter_t* efp) {
ray_op_ext_t* ext = find_ext(g, op->id);
uint32_t n_keys = ext->n_keys, n_aggs = ext->n_aggs;
ray_pool_t* pool = ray_pool_get();
uint32_t nw = ray_pool_total_workers(pool);
agg_desc_t d;
if (!agg_desc_init(&d, g, ext, tbl, key_cols)) return ray_error("oom", NULL);
const void** key_data = d.key_data;
const void** val_data = d.val_data; const int8_t* val_types = d.val_types;
const bool* val_hasnull = d.val_hasnull; const uint8_t* val_esz = d.val_esz;
const void** val2_data = d.val2_data; const int8_t* val2_types = d.val2_types;
const bool* val2_hasnull = d.val2_hasnull; const uint8_t* val2_esz = d.val2_esz;
bool needs_row = true;
ray_t* off_hdr;
size_t* val_off = (size_t*)scratch_calloc(&off_hdr,
2u * (size_t)(n_aggs ? n_aggs : 1) * sizeof(size_t));
if (!val_off) { agg_desc_free(&d); return ray_error("oom", NULL); }
size_t* val2_off = val_off + n_aggs;
size_t rec_cur = (size_t)n_keys * 8;
for (uint32_t a = 0; a < n_aggs; a++) {
if (val_data[a]) { val_off[a] = rec_cur; rec_cur += val_esz[a]; }
if (val2_data[a]) { val2_off[a] = rec_cur; rec_cur += val2_esz[a]; }
}
size_t row_off = rec_cur; if (needs_row) rec_cur += 8;
size_t rec = (rec_cur + 7u) & ~(size_t)7u;
int64_t n_in = sel ? n_sel : nrows;
if (n_in < 0) n_in = 0;
uint32_t n_parts = agg_radix_part_count(nw, n_in);
if (n_parts > 65536) n_parts = 65536;
uint64_t want = (uint64_t)nw * AGG_RADIX_CHUNKS_PER_WORKER;
uint64_t by_budget = AGG_RADIX_COUNT_BUDGET / ((uint64_t)n_parts * sizeof(uint32_t));
if (want > by_budget) want = by_budget;
if (want < nw) want = nw;
uint64_t by_rows = ((uint64_t)n_in + AGG_RADIX_CHUNK_MIN_ROWS - 1) / AGG_RADIX_CHUNK_MIN_ROWS;
if (by_rows < nw && (uint64_t)n_in >= (uint64_t)nw * AGG_RADIX_CHUNK_FLOOR_ROWS)
by_rows = nw;
if (want > by_rows) want = by_rows;
uint64_t by_width = (uint64_t)n_in / ((uint64_t)1 << 30) + 1;
if (want < by_width) want = by_width;
if (want < 1) want = 1;
int64_t chunk_rows = (int64_t)(((uint64_t)n_in + want - 1) / want);
if (chunk_rows < 1) chunk_rows = 1;
uint32_t n_chunks = (uint32_t)((n_in + chunk_rows - 1) / chunk_rows);
agg_radix_part_t* parts = ray_calloc_raw((size_t)n_parts * sizeof(agg_radix_part_t));
uint32_t* cnt = ray_alloc_raw((size_t)(n_chunks ? n_chunks : 1) * n_parts * sizeof(uint32_t));
int64_t* part_start = ray_alloc_raw(((size_t)n_parts + 1) * sizeof(int64_t));
uint16_t* pid = ray_alloc_raw((size_t)(n_in ? n_in : 1) * sizeof(uint16_t));
char* pay = ray_alloc_raw((size_t)(n_in ? n_in : 1) * rec);
if (!parts || !cnt || !part_start || !pid || !pay) {
ray_free_raw(parts); ray_free_raw(cnt); ray_free_raw(part_start);
ray_free_raw(pid); ray_free_raw(pay);
scratch_free(off_hdr); agg_desc_free(&d);
return exec_group_v2_parallel_smallhash(g, op, tbl, nrows,
key_cols, key_syms, vts, off, block, sel, sel_prefix, n_sel);
}
part_start[0] = 0;
agg_radix_ctx_t ctx = {
.key_cols = key_cols, .key_data = key_data, .n_keys = n_keys,
.vts = vts, .off = off, .block = block, .n_aggs = n_aggs,
.val_data = val_data, .val_types = val_types, .val_hasnull = val_hasnull, .val_esz = val_esz,
.val2_data = val2_data, .val2_types = val2_types, .val2_hasnull = val2_hasnull, .val2_esz = val2_esz,
.nw = nw, .n_parts = n_parts,
.part_bits = (uint32_t)__builtin_ctz(n_parts),
.n_in = n_in, .chunk_rows = chunk_rows, .n_chunks = n_chunks,
.cnt = cnt, .part_start = part_start, .pay = pay, .pid = pid,
.parts = parts,
.rec = rec, .row_off = row_off, .needs_row = needs_row,
.sel = sel, .sel_prefix = sel_prefix,
.val_off = val_off, .val2_off = val2_off,
};
atomic_init(&ctx.part_overflow, 0);
ray_pool_dispatch_n(pool, agg_radix_count_fn, &ctx, n_chunks);
bool cancelled = pool_cancelled(pool);
if (!cancelled) {
uint32_t n_bands = (n_parts + AGG_RADIX_PREFIX_BAND - 1) / AGG_RADIX_PREFIX_BAND;
if (n_bands > 1 && (uint64_t)n_chunks * n_parts >= ((uint64_t)1 << 16))
ray_pool_dispatch_n(pool, agg_radix_prefix_fn, &ctx, n_bands);
else
agg_radix_prefix_fn(&ctx, 0, 0, n_bands);
cancelled = pool_cancelled(pool);
}
int oom = atomic_load_explicit(&ctx.part_overflow, memory_order_relaxed);
if (!cancelled && !oom) {
for (uint32_t p = 0; p < n_parts; p++) part_start[p + 1] += part_start[p];
ray_pool_dispatch_n(pool, agg_radix_scatter_fn, &ctx, n_chunks);
cancelled = pool_cancelled(pool);
}
ray_free_raw(cnt); ctx.cnt = NULL;
ray_free_raw(pid); ctx.pid = NULL;
ray_profile_tick("radix: scattered rows");
if (!cancelled && !oom)
ray_pool_dispatch_n(pool, agg_radix_group_fn, &ctx, n_parts);
if (!cancelled && !oom) {
cancelled = pool_cancelled(pool);
for (uint32_t p = 0; p < n_parts; p++)
if (parts[p].oom) { oom = 1; break; }
}
ray_profile_tick("radix: reduced partitions");
scratch_free(off_hdr);
ray_free_raw(pay); ctx.pay = NULL;
ray_free_raw(part_start); ctx.part_start = NULL;
if (cancelled || oom) {
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(&d);
if (cancelled) return ray_error("cancel", NULL);
return exec_group_v2_parallel_smallhash(g, op, tbl, nrows,
key_cols, key_syms, vts, off, block, sel, sel_prefix, n_sel);
}
return agg_parts_emit(ext, &d, parts, n_parts, pool, nw, key_cols, key_syms,
vts, off, block, sel ? n_sel : nrows, group_limit, efp);
}
#define AGG_TS_CHUNK 4096
#define AGG_TS_MIN_BITS 13
#define AGG_TS_MAX_BITS 15
#define AGG_TS_LOAD_NUM 3
#define AGG_TS_SWITCH_ROWS 16384
#define AGG_TS_SWITCH_PCT 50
#define AGG_TS_TASK_ROWS 16384
#define AGG_TS_TASKS_PER_WORKER 2
#define AGG_TS_BUCKET_FIRST ((size_t)8 << 10)
#define AGG_TS_BUCKET_NEXT ((size_t)64 << 10)
#define AGG_TS_SAMPLE 8192
#define AGG_TS_GATE_UNSEEN_PCT 66
#define AGG_TS_GATE_GROUPS 24576
#define AGG_TS_GATE_REPEAT 4
#define AGG_TS_KEY_OFF 16
typedef struct agg_ts_table {
uint8_t* tag;
char* ent;
uint32_t bits;
int64_t cap;
int64_t ng;
int64_t rows;
struct agg_ts_table* prev;
} agg_ts_table_t;
typedef struct agg_ts_chunk {
struct agg_ts_chunk* next;
int64_t n;
int64_t cap;
} agg_ts_chunk_t;
typedef struct {
agg_ts_chunk_t* head;
agg_ts_chunk_t* tail;
int64_t n;
size_t bytes;
} agg_ts_bucket_t;
typedef struct {
agg_ts_table_t* top;
agg_ts_bucket_t* buckets;
int64_t rows;
int64_t entries;
int64_t scattered;
uint32_t n_tables;
bool scatter;
int oom;
} agg_ts_task_t;
typedef struct {
ray_t** key_cols;
const void** key_data;
uint32_t n_keys;
const agg_vtable_t** vts;
const size_t* off;
size_t block;
uint32_t n_aggs;
const void** val_data; const int8_t* val_types; const bool* val_hasnull; const uint8_t* val_esz;
const void** val2_data; const int8_t* val2_types; const bool* val2_hasnull; const uint8_t* val2_esz;
agg_valdesc_t vd;
const size_t* ent_off;
size_t estride;
size_t state_off;
size_t rec;
size_t row_off;
const size_t* val_off;
const size_t* val2_off;
uint32_t n_parts;
uint32_t part_bits;
uint32_t init_bits;
int64_t n_in;
int64_t task_rows;
uint32_t n_tasks;
agg_ts_task_t* tasks;
agg_radix_part_t* parts;
int64_t merge_cand;
ray_t* sel;
const int64_t* sel_prefix;
} agg_ts_ctx_t;
static inline uint64_t agg_ts_hash(const int64_t* keys, uint32_t n_keys) {
uint64_t h = 1469598103934665603ULL;
for (uint32_t k = 0; k < n_keys; k++) { h ^= (uint64_t)keys[k]; h *= 1099511628211ULL; }
return agg_radix_fmix64(h) | 1u;
}
static inline uint8_t agg_ts_tag(uint64_t h) { return (uint8_t)((h >> 48) | 1u); }
static inline uint64_t agg_ts_ent_hash(const agg_ts_table_t* t, size_t estride, int64_t slot) {
uint64_t h; memcpy(&h, t->ent + (size_t)slot * estride, sizeof(h)); return h;
}
static agg_ts_table_t* agg_ts_table_new(uint32_t bits, size_t estride) {
agg_ts_table_t* t = ray_alloc_raw(sizeof(agg_ts_table_t));
if (!t) return NULL;
int64_t cap = (int64_t)1 << bits;
t->tag = ray_calloc_raw((size_t)cap);
t->ent = ray_alloc_raw((size_t)cap * estride);
if (!t->tag || !t->ent) {
ray_free_raw(t->tag); ray_free_raw(t->ent); ray_free_raw(t);
return NULL;
}
t->bits = bits; t->cap = cap; t->ng = 0; t->rows = 0; t->prev = NULL;
return t;
}
static void agg_ts_table_free(agg_ts_table_t* t) {
while (t) {
agg_ts_table_t* prev = t->prev;
ray_free_raw(t->tag); ray_free_raw(t->ent); ray_free_raw(t);
t = prev;
}
}
static agg_ts_table_t* agg_ts_table_grow(agg_ts_table_t* t, size_t estride) {
agg_ts_table_t* n = agg_ts_table_new(t->bits + 1, estride);
if (!n) return NULL;
uint64_t nmask = (uint64_t)n->cap - 1;
uint32_t shift = 64 - n->bits;
for (int64_t s = 0; s < t->cap; s++) {
if (!t->tag[s]) continue;
uint64_t h = agg_ts_ent_hash(t, estride, s);
uint64_t d = h >> shift;
while (n->tag[d]) d = (d + 1) & nmask;
n->tag[d] = t->tag[s];
memcpy(n->ent + (size_t)d * estride, t->ent + (size_t)s * estride, estride);
}
n->ng = t->ng; n->rows = t->rows; n->prev = t->prev;
ray_free_raw(t->tag); ray_free_raw(t->ent); ray_free_raw(t);
return n;
}
static void agg_ts_bucket_free(agg_ts_bucket_t* b) {
agg_ts_chunk_t* ch = b->head;
while (ch) { agg_ts_chunk_t* nx = ch->next; ray_free_raw(ch); ch = nx; }
b->head = b->tail = NULL; b->n = 0; b->bytes = 0;
}
static void agg_ts_task_free(agg_ts_task_t* ts, uint32_t n_parts) {
agg_ts_table_free(ts->top); ts->top = NULL;
if (ts->buckets) {
for (uint32_t p = 0; p < n_parts; p++) agg_ts_bucket_free(&ts->buckets[p]);
ray_free_raw(ts->buckets); ts->buckets = NULL;
}
}
static inline char* agg_ts_bucket_push(agg_ts_bucket_t* b, size_t rec) {
agg_ts_chunk_t* ch = b->tail;
if (!ch || ch->n == ch->cap) {
size_t bytes = ch ? b->bytes * 2 : AGG_TS_BUCKET_FIRST;
if (bytes > AGG_TS_BUCKET_NEXT) bytes = AGG_TS_BUCKET_NEXT;
b->bytes = bytes;
int64_t cap = (int64_t)((bytes - sizeof(agg_ts_chunk_t)) / rec);
if (cap < 1) cap = 1;
agg_ts_chunk_t* nc = ray_alloc_raw(sizeof(agg_ts_chunk_t) + (size_t)cap * rec);
if (!nc) return NULL;
nc->next = NULL; nc->n = 0; nc->cap = cap;
if (ch) ch->next = nc; else b->head = nc;
b->tail = nc; ch = nc;
}
b->n++;
return (char*)(ch + 1) + (size_t)ch->n++ * rec;
}
static bool agg_ts_reserve(agg_ts_ctx_t* c, agg_ts_task_t* ts, int64_t n) {
agg_ts_table_t* t = ts->top;
while (!t || t->ng + n > (t->cap >> 2) * AGG_TS_LOAD_NUM) {
if (t && t->rows >= AGG_TS_SWITCH_ROWS &&
t->ng * 100 >= t->rows * AGG_TS_SWITCH_PCT) {
ts->scatter = true;
return true;
}
if (!t) {
t = agg_ts_table_new(c->init_bits, c->estride);
if (!t) return false;
ts->n_tables++;
} else if (t->bits < AGG_TS_MAX_BITS) {
agg_ts_table_t* grown = agg_ts_table_grow(t, c->estride);
if (!grown) return false;
t = grown;
} else {
agg_ts_table_t* nt = agg_ts_table_new(AGG_TS_MAX_BITS, c->estride);
if (!nt) return false;
nt->prev = t; t = nt;
ts->n_tables++;
}
ts->top = t;
}
return true;
}
static void agg_ts_chunk(agg_ts_ctx_t* c, agg_ts_task_t* ts,
const int64_t* rows, int64_t pos0, int64_t n,
agg_sel_scratch_t* sc, uint32_t* gid, int64_t* keys) {
uint32_t n_keys = c->n_keys, n_aggs = c->n_aggs;
if (!ts->scatter && !agg_ts_reserve(c, ts, n)) { ts->oom = 1; return; }
if (ts->scatter) {
if (!ts->buckets) {
ts->buckets = ray_calloc_raw((size_t)c->n_parts * sizeof(agg_ts_bucket_t));
if (!ts->buckets) { ts->oom = 1; return; }
}
uint32_t pshift = 64 - c->part_bits;
for (int64_t i = 0; i < n; i++) {
int64_t r = rows ? rows[i] : pos0 + i;
for (uint32_t k = 0; k < n_keys; k++)
keys[k] = agg_read_key_i64(c->key_cols[k], c->key_data[k], r);
uint64_t h = agg_ts_hash(keys, n_keys);
char* rec = agg_ts_bucket_push(&ts->buckets[h >> pshift], c->rec);
if (!rec) { ts->oom = 1; return; }
memcpy(rec, keys, (size_t)n_keys * sizeof(int64_t));
for (uint32_t a = 0; a < n_aggs; a++) {
if (c->val_data[a]) {
uint8_t ez = c->val_esz[a];
memcpy(rec + c->val_off[a], (const char*)c->val_data[a] + (size_t)r * ez, ez);
}
if (c->val2_data[a]) {
uint8_t ez2 = c->val2_esz[a];
memcpy(rec + c->val2_off[a], (const char*)c->val2_data[a] + (size_t)r * ez2, ez2);
}
}
int64_t pos = pos0 + i;
memcpy(rec + c->row_off, &pos, sizeof(pos));
}
ts->rows += n; ts->scattered += n;
return;
}
agg_ts_table_t* t = ts->top;
uint64_t mask = (uint64_t)t->cap - 1;
uint32_t shift = 64 - t->bits;
size_t estride = c->estride, key_bytes = (size_t)n_keys * sizeof(int64_t);
for (int64_t i = 0; i < n; i++) {
int64_t r = rows ? rows[i] : pos0 + i;
for (uint32_t k = 0; k < n_keys; k++)
keys[k] = agg_read_key_i64(c->key_cols[k], c->key_data[k], r);
uint64_t h = agg_ts_hash(keys, n_keys);
uint8_t tag = agg_ts_tag(h);
uint64_t slot = h >> shift;
for (;;) {
uint8_t st = t->tag[slot];
if (!st) {
char* e = t->ent + (size_t)slot * estride;
t->tag[slot] = tag;
int64_t pos = pos0 + i;
memcpy(e, &h, sizeof(h));
memcpy(e + sizeof(uint64_t), &pos, sizeof(pos));
memcpy(e + AGG_TS_KEY_OFF, keys, key_bytes);
for (uint32_t a = 0; a < n_aggs; a++)
c->vts[a]->init(e + c->ent_off[a]);
t->ng++;
break;
}
if (st == tag) {
const char* e = t->ent + (size_t)slot * estride;
uint64_t eh; memcpy(&eh, e, sizeof(eh));
if (eh == h && memcmp(e + AGG_TS_KEY_OFF, keys, key_bytes) == 0) break;
}
slot = (slot + 1) & mask;
}
gid[i] = (uint32_t)slot;
}
if (rows) {
if (agg_sel_accum_chunk(&c->vd, sc, t->ent, gid, rows, n) != 0) { ts->oom = 1; return; }
} else {
for (uint32_t a = 0; a < n_aggs; a++) {
char* states = t->ent + c->ent_off[a];
if (c->vts[a]->update_batch2) {
const void* vx = (const char*)c->val_data[a] + (size_t)pos0 * c->val_esz[a];
const void* vy = (const char*)c->val2_data[a] + (size_t)pos0 * c->val2_esz[a];
ray_valid_t valid_x = { vx, c->val_types[a], c->val_hasnull[a] };
ray_valid_t valid_y = { vy, c->val2_types[a], c->val2_hasnull[a] };
c->vts[a]->update_batch2(states, estride, gid, vx, vy, &valid_x, &valid_y, n, NULL);
} else if (c->val_data[a]) {
const void* vals = (const char*)c->val_data[a] + (size_t)pos0 * c->val_esz[a];
ray_valid_t valid = { vals, c->val_types[a], c->val_hasnull[a] };
c->vts[a]->update_batch(states, estride, gid, vals, &valid, n, NULL);
} else {
ray_valid_t valid = { NULL, c->val_types[a], false };
c->vts[a]->update_batch(states, estride, gid, NULL, &valid, n, NULL);
}
}
}
t->rows += n; ts->rows += n;
}
static void agg_ts_phase1_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_ts_ctx_t* c = (agg_ts_ctx_t*)vctx;
ray_t* kb_hdr = NULL;
int64_t* keys = (int64_t*)scratch_alloc(&kb_hdr,
(size_t)(c->n_keys ? c->n_keys : 1) * sizeof(int64_t));
uint32_t* gid = keys ? ray_alloc_raw(AGG_TS_CHUNK * sizeof(uint32_t)) : NULL;
if (!keys || !gid) {
for (int64_t ti = start; ti < end; ti++) c->tasks[ti].oom = 1;
ray_free_raw(gid); scratch_free(kb_hdr);
return;
}
for (int64_t ti = start; ti < end; ti++) {
agg_ts_task_t* ts = &c->tasks[ti];
int64_t lo = ti * c->task_rows;
int64_t hi = lo + c->task_rows;
if (hi > c->n_in) hi = c->n_in;
if (c->sel) {
int64_t rows[AGG_SEL_CHUNK];
agg_sel_scratch_t sc;
if (!agg_sel_scratch_init(&sc, c->n_aggs)) { ts->oom = 1; continue; }
agg_sel_cursor_t cur;
agg_sel_cursor_init(&cur, c->sel, c->sel_prefix, lo, hi);
int64_t pos = lo, cn;
while (!ts->oom && (cn = agg_sel_cursor_next(&cur, rows)) > 0) {
agg_ts_chunk(c, ts, rows, pos, cn, &sc, gid, keys);
pos += cn;
}
agg_sel_scratch_free(&sc);
} else {
for (int64_t cs = lo; cs < hi && !ts->oom; cs += AGG_TS_CHUNK) {
int64_t ce = cs + AGG_TS_CHUNK; if (ce > hi) ce = hi;
agg_ts_chunk(c, ts, NULL, cs, ce - cs, NULL, gid, keys);
}
}
for (agg_ts_table_t* tab = ts->top; tab; tab = tab->prev) ts->entries += tab->ng;
}
ray_free_raw(gid); scratch_free(kb_hdr);
}
typedef struct {
uint64_t* mh;
int32_t* mg;
int64_t htcap;
char* states;
int64_t* first_row;
int64_t* keys;
int64_t gcap, ng;
} agg_ts_merge_t;
static bool agg_ts_merge_init(agg_ts_merge_t* m, int64_t expect, size_t block, uint32_t n_keys) {
int64_t htcap = 64;
while (htcap < expect * 4) htcap <<= 1;
int64_t gcap = expect < 256 ? expect : 256;
if (gcap < 16) gcap = 16;
m->mh = ray_calloc_raw((size_t)htcap * sizeof(uint64_t));
m->mg = ray_alloc_raw((size_t)htcap * sizeof(int32_t));
m->states = ray_alloc_raw((size_t)gcap * block);
m->first_row = ray_alloc_raw((size_t)gcap * sizeof(int64_t));
m->keys = ray_alloc_raw((size_t)gcap * (size_t)(n_keys ? n_keys : 1) * sizeof(int64_t));
m->htcap = htcap; m->gcap = gcap; m->ng = 0;
return m->mh && m->mg && m->states && m->first_row && m->keys;
}
static void agg_ts_merge_free(agg_ts_merge_t* m, bool keep_groups) {
ray_free_raw(m->mh); ray_free_raw(m->mg); m->mh = NULL; m->mg = NULL;
if (!keep_groups) {
ray_free_raw(m->states); ray_free_raw(m->first_row); ray_free_raw(m->keys);
m->states = NULL; m->first_row = NULL; m->keys = NULL;
}
}
#define AGG_TS_MSLOT(h, mask) (((h) >> 1) & (mask))
static bool agg_ts_merge_rehash(agg_ts_merge_t* m) {
int64_t ncap = m->htcap * 2;
uint64_t* nh = ray_calloc_raw((size_t)ncap * sizeof(uint64_t));
int32_t* ng = ray_alloc_raw((size_t)ncap * sizeof(int32_t));
if (!nh || !ng) { ray_free_raw(nh); ray_free_raw(ng); return false; }
uint64_t nmask = (uint64_t)ncap - 1;
for (int64_t s = 0; s < m->htcap; s++) {
uint64_t h = m->mh[s];
if (!h) continue;
uint64_t d = AGG_TS_MSLOT(h, nmask);
while (nh[d]) d = (d + 1) & nmask;
nh[d] = h; ng[d] = m->mg[s];
}
ray_free_raw(m->mh); ray_free_raw(m->mg);
m->mh = nh; m->mg = ng; m->htcap = ncap;
return true;
}
static inline int64_t agg_ts_merge_gid(agg_ts_merge_t* m, agg_ts_ctx_t* c,
uint64_t h, const int64_t* keys, int64_t pos) {
uint32_t n_keys = c->n_keys;
size_t key_bytes = (size_t)n_keys * sizeof(int64_t);
uint64_t mask = (uint64_t)m->htcap - 1;
uint64_t slot = AGG_TS_MSLOT(h, mask);
for (;;) {
uint64_t sh = m->mh[slot];
if (!sh) break;
int32_t gg = m->mg[slot];
if (sh == h && memcmp(m->keys + (size_t)gg * n_keys, keys, key_bytes) == 0) {
if (pos < m->first_row[gg]) m->first_row[gg] = pos;
return gg;
}
slot = (slot + 1) & mask;
}
if ((m->ng + 1) * 2 > m->htcap) {
if (!agg_ts_merge_rehash(m)) return -1;
mask = (uint64_t)m->htcap - 1;
slot = AGG_TS_MSLOT(h, mask);
while (m->mh[slot]) slot = (slot + 1) & mask;
}
if (m->ng == m->gcap) {
int64_t ncap = m->gcap * 2;
void* ns = ray_realloc_raw(m->states, (size_t)ncap * c->block);
if (!ns) return -1;
m->states = ns;
void* nf = ray_realloc_raw(m->first_row, (size_t)ncap * sizeof(int64_t));
if (!nf) return -1;
m->first_row = nf;
void* nk = ray_realloc_raw(m->keys, (size_t)ncap * (size_t)(n_keys ? n_keys : 1) * sizeof(int64_t));
if (!nk) return -1;
m->keys = nk;
m->gcap = ncap;
}
int64_t gg = m->ng++;
m->mh[slot] = h; m->mg[slot] = (int32_t)gg;
m->first_row[gg] = pos;
memcpy(m->keys + (size_t)gg * n_keys, keys, key_bytes);
for (uint32_t a = 0; a < c->n_aggs; a++)
c->vts[a]->init(m->states + (size_t)gg * c->block + c->off[a]);
return gg;
}
#define AGG_TS_RANGE_FOREACH(tab, p, part_bits, SLOT, BODY) do { \
agg_ts_table_t* tab_ = (tab); \
uint64_t mask_ = (uint64_t)tab_->cap - 1; \
uint32_t span_ = tab_->bits - (part_bits); \
int64_t lo_ = (int64_t)(p) << span_, n_ = (int64_t)1 << span_; \
int64_t s_ = lo_, end_ = lo_ + n_; \
for (; s_ < end_; s_++) { \
if (!tab_->tag[s_]) continue; \
int64_t SLOT = s_; BODY \
} \
for (int64_t k_ = 0; k_ < tab_->cap && tab_->tag[(uint64_t)s_ & mask_]; \
k_++, s_++) { int64_t SLOT = (int64_t)((uint64_t)s_ & mask_); BODY } \
} while (0)
static void agg_ts_merge_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_ts_ctx_t* c = (agg_ts_ctx_t*)vctx;
uint32_t n_keys = c->n_keys, n_aggs = c->n_aggs;
uint32_t pshift = 64 - c->part_bits;
ray_t* ptr_hdr = NULL;
char** gv = (char**)scratch_calloc(&ptr_hdr,
(size_t)(2u * (n_aggs ? n_aggs : 1)) * sizeof(char*));
uint32_t* gid = gv ? ray_alloc_raw(AGG_TS_CHUNK * sizeof(uint32_t)) : NULL;
bool ok = gv && gid;
char** gy = gv ? gv + (n_aggs ? n_aggs : 1) : NULL;
for (uint32_t a = 0; ok && a < n_aggs; a++) {
if (c->val_data[a]) { gv[a] = ray_alloc_raw((size_t)AGG_TS_CHUNK * (c->val_esz[a] ? c->val_esz[a] : 1)); ok = gv[a] != NULL; }
if (ok && c->val2_data[a]) { gy[a] = ray_alloc_raw((size_t)AGG_TS_CHUNK * (c->val2_esz[a] ? c->val2_esz[a] : 1)); ok = gy[a] != NULL; }
}
uint32_t depth_cap = 16;
agg_ts_table_t** stack = ok ? ray_alloc_raw(depth_cap * sizeof(agg_ts_table_t*)) : NULL;
ok = ok && stack;
int64_t expect = c->merge_cand / c->n_parts + 1;
for (int64_t pi = start; pi < end; pi++) {
uint32_t p = (uint32_t)pi;
agg_radix_part_t* pr = &c->parts[p];
if (!ok) { pr->oom = 1; continue; }
agg_ts_merge_t m;
if (!agg_ts_merge_init(&m, expect, c->block, n_keys)) {
agg_ts_merge_free(&m, false); pr->oom = 1; continue;
}
bool poom = false;
for (uint32_t ti = 0; ti < c->n_tasks && !poom; ti++) {
agg_ts_task_t* ts = &c->tasks[ti];
uint32_t depth = 0;
for (agg_ts_table_t* tab = ts->top; tab; tab = tab->prev) {
if (depth == depth_cap) {
void* ns = ray_realloc_raw((void*)stack, (size_t)depth_cap * 2 * sizeof(agg_ts_table_t*));
if (!ns) { poom = true; break; }
stack = ns; depth_cap *= 2;
}
stack[depth++] = tab;
}
if (poom) break;
for (uint32_t di = depth; di-- > 0 && !poom; ) {
agg_ts_table_t* tab = stack[di];
AGG_TS_RANGE_FOREACH(tab, p, c->part_bits, s, {
if (poom) break;
const char* e = tab_->ent + (size_t)s * c->estride;
uint64_t h; memcpy(&h, e, sizeof(h));
if ((uint32_t)(h >> pshift) == p) {
int64_t pos; memcpy(&pos, e + sizeof(uint64_t), sizeof(pos));
int64_t gg = agg_ts_merge_gid(&m, c, h,
(const int64_t*)(e + AGG_TS_KEY_OFF), pos);
if (gg < 0) { poom = true; break; }
for (uint32_t a = 0; a < n_aggs; a++)
c->vts[a]->merge(m.states + (size_t)gg * c->block + c->off[a],
e + c->ent_off[a], NULL);
}
});
}
if (poom || !ts->buckets) continue;
agg_ts_bucket_t* b = &ts->buckets[p];
for (agg_ts_chunk_t* ch = b->head; ch && !poom; ch = ch->next) {
const char* rec = (const char*)(ch + 1);
for (int64_t base = 0; base < ch->n && !poom; base += AGG_TS_CHUNK) {
int64_t bn = ch->n - base; if (bn > AGG_TS_CHUNK) bn = AGG_TS_CHUNK;
for (int64_t i = 0; i < bn; i++, rec += c->rec) {
const int64_t* keys = (const int64_t*)rec;
int64_t pos; memcpy(&pos, rec + c->row_off, sizeof(pos));
for (uint32_t a = 0; a < n_aggs; a++) {
if (gv[a]) memcpy(gv[a] + (size_t)i * c->val_esz[a], rec + c->val_off[a], c->val_esz[a]);
if (gy[a]) memcpy(gy[a] + (size_t)i * c->val2_esz[a], rec + c->val2_off[a], c->val2_esz[a]);
}
int64_t gg = agg_ts_merge_gid(&m, c, agg_ts_hash(keys, n_keys), keys, pos);
if (gg < 0) { poom = true; break; }
gid[i] = (uint32_t)gg;
}
if (poom) break;
for (uint32_t a = 0; a < n_aggs; a++) {
char* states = m.states + c->off[a];
if (c->vts[a]->update_batch2) {
ray_valid_t vx = { gv[a], c->val_types[a], c->val_hasnull[a] };
ray_valid_t vy = { gy[a], c->val2_types[a], c->val2_hasnull[a] };
c->vts[a]->update_batch2(states, c->block, gid, gv[a], gy[a], &vx, &vy, bn, NULL);
} else if (c->val_data[a]) {
ray_valid_t valid = { gv[a], c->val_types[a], c->val_hasnull[a] };
c->vts[a]->update_batch(states, c->block, gid, gv[a], &valid, bn, NULL);
} else {
ray_valid_t valid = { NULL, c->val_types[a], false };
c->vts[a]->update_batch(states, c->block, gid, NULL, &valid, bn, NULL);
}
}
}
}
agg_ts_bucket_free(b);
}
if (poom) { agg_ts_merge_free(&m, false); pr->oom = 1; continue; }
agg_ts_merge_free(&m, true);
pr->states = m.states; pr->first_row = m.first_row; pr->keys = m.keys; pr->ng = m.ng;
}
ray_free_raw(stack);
for (uint32_t a = 0; a < n_aggs && gv; a++) { ray_free_raw(gv[a]); ray_free_raw(gy[a]); }
ray_free_raw(gid); scratch_free(ptr_hdr);
}
#define AGG_TS_SAMPLE_TASKS 16
#define AGG_TS_SAMPLE_PER (AGG_TS_SAMPLE / AGG_TS_SAMPLE_TASKS)
#define AGG_TS_SAMPLE_SETCAP (AGG_TS_SAMPLE_PER * 4)
#define AGG_TS_PRE_SAMPLE 512
#define AGG_TS_PRE_REPEATS 1
#define AGG_TS_PRE_CLEAR 32
#define AGG_TS_PRE_SAMPLE2 1024
#define AGG_TS_PRE_DECLINE_X 1.5
typedef struct {
ray_t** key_cols;
const void** key_data;
uint32_t n_keys;
int64_t n_in;
int64_t step;
ray_t* sel;
const int64_t* sel_prefix;
uint64_t* hs;
uint16_t* cnt;
int64_t* keys;
int64_t* n;
} agg_ts_sample_ctx_t;
static void agg_ts_sample_fn(void* vctx, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
agg_ts_sample_ctx_t* c = (agg_ts_sample_ctx_t*)vctx;
uint32_t n_keys = c->n_keys;
for (int64_t t = start; t < end; t++) {
uint64_t* hs = c->hs + (size_t)t * AGG_TS_SAMPLE_SETCAP;
uint16_t* cnt = c->cnt + (size_t)t * AGG_TS_SAMPLE_SETCAP;
int64_t* keys = c->keys + (size_t)t * (n_keys ? n_keys : 1);
int64_t rows[AGG_SEL_CHUNK];
int64_t n = 0;
for (int64_t j = 0; j < AGG_TS_SAMPLE_PER; j++) {
int64_t i = t * AGG_TS_SAMPLE_PER + j;
int64_t pos = i * c->step + (int64_t)(ray_hash_i64(i) % (uint64_t)c->step);
int64_t r = pos;
if (c->sel) {
agg_sel_cursor_t cur;
agg_sel_cursor_init(&cur, c->sel, c->sel_prefix, pos, pos + 1);
if (agg_sel_cursor_next(&cur, rows) != 1) continue;
r = rows[0];
}
for (uint32_t k = 0; k < n_keys; k++)
keys[k] = agg_read_key_i64(c->key_cols[k], c->key_data[k], r);
uint64_t h = agg_ts_hash(keys, n_keys);
uint64_t at = h & (AGG_TS_SAMPLE_SETCAP - 1);
while (hs[at] && hs[at] != h) at = (at + 1) & (AGG_TS_SAMPLE_SETCAP - 1);
if (!hs[at]) hs[at] = h;
cnt[at]++;
n++;
}
c->n[t] = n;
}
}
static inline bool agg_ts_sel_row_at(const uint8_t* flags, const uint32_t* offsets,
const uint16_t* idx, int64_t pos, uint64_t salt,
int64_t* row) {
uint32_t seg = (uint32_t)(pos / RAY_MORSEL_ELEMS);
uint8_t f = flags[seg];
if (f == RAY_SEL_NONE) return false;
if (f == RAY_SEL_ALL) { *row = pos; return true; }
uint32_t len = offsets[seg + 1] - offsets[seg];
if (len == 0) return false;
const uint16_t* slice = idx + offsets[seg];
*row = (int64_t)seg * RAY_MORSEL_ELEMS + slice[salt % len];
return true;
}
static bool agg_ts_precheck_declines(ray_t** key_cols, uint32_t n_keys, int64_t nrows,
ray_t* sel, const int64_t* sel_prefix, int64_t n_sel) {
(void)sel_prefix;
enum { PRECAP = AGG_TS_PRE_SAMPLE2 * 4 };
int64_t n_in = sel ? n_sel : nrows;
if (n_in < AGG_TS_SAMPLE) return false;
const uint8_t* sflags = sel ? ray_rowsel_flags(sel) : NULL;
const uint32_t* soffsets = sel ? ray_rowsel_offsets(sel) : NULL;
const uint16_t* sidx = sel ? ray_rowsel_idx(sel) : NULL;
size_t nk = n_keys ? n_keys : 1;
uint64_t phs[PRECAP];
int64_t pkeys[16];
const void* pdata[16];
ray_t* pk_hdr = NULL;
int64_t* keys = pkeys;
const void** key_data = pdata;
if (nk > 16) {
keys = (int64_t*)scratch_alloc(&pk_hdr, nk * (sizeof(int64_t) + sizeof(void*)));
if (!keys) return false;
key_data = (const void**)(keys + nk);
}
for (uint32_t k = 0; k < n_keys; k++) key_data[k] = ray_data(key_cols[k]);
memset(phs, 0, sizeof(phs));
const int64_t ratio = AGG_TS_PRE_SAMPLE2 / AGG_TS_PRE_SAMPLE;
int64_t pstep = nrows / AGG_TS_PRE_SAMPLE2;
if (pstep < 1) pstep = 1;
int64_t d = 0, n = 0;
bool declined = false;
for (int64_t phase = 0; phase < ratio && !declined; phase++) {
for (int64_t j = 0; j < AGG_TS_PRE_SAMPLE; j++) {
int64_t i = j * ratio + phase;
uint64_t jit = ray_hash_i64(i + 7919);
int64_t pos = i * pstep + (int64_t)(jit % (uint64_t)pstep);
if (pos >= nrows) break;
int64_t r = pos;
if (sel && !agg_ts_sel_row_at(sflags, soffsets, sidx, pos, jit >> 32, &r)) continue;
for (uint32_t k = 0; k < n_keys; k++)
keys[k] = agg_read_key_i64(key_cols[k], key_data[k], r);
uint64_t h = agg_ts_hash(keys, n_keys);
uint64_t at = h & (PRECAP - 1);
while (phs[at] && phs[at] != h) at = (at + 1) & (PRECAP - 1);
if (!phs[at]) { phs[at] = h; d++; }
n++;
}
if (n < AGG_TS_PRE_SAMPLE / 2) break;
int64_t repeats = n - d;
if (phase == 0) {
if (repeats <= AGG_TS_PRE_REPEATS) declined = true;
else if (repeats >= AGG_TS_PRE_CLEAR) break;
}
}
if (!declined && n >= AGG_TS_PRE_SAMPLE2 / 2) {
int64_t repeats = n - d;
double birthday = repeats > 0 ? (double)n * (double)n / (2.0 * (double)repeats) : 9.2e18;
declined = birthday >= AGG_TS_PRE_DECLINE_X * (double)AGG_TS_GATE_GROUPS;
}
scratch_free(pk_hdr);
return declined;
}
static double agg_ts_estimate(ray_pool_t* pool, ray_t** key_cols, const void** key_data,
uint32_t n_keys, int64_t nrows,
ray_t* sel, const int64_t* sel_prefix, int64_t n_sel,
int* unseen_pct) {
enum { CAP = 16384, PRECAP = AGG_TS_PRE_SAMPLE * 4 };
int64_t n_in = sel ? n_sel : nrows;
*unseen_pct = 0;
if (n_in < AGG_TS_SAMPLE) return (double)n_in;
size_t nk = n_keys ? n_keys : 1;
size_t set_bytes = (size_t)AGG_TS_SAMPLE_TASKS * AGG_TS_SAMPLE_SETCAP;
ray_t* hdr = NULL;
uint64_t* hs = (uint64_t*)scratch_calloc(&hdr,
set_bytes * sizeof(uint64_t) + set_bytes * sizeof(uint16_t)
+ (size_t)AGG_TS_SAMPLE_TASKS * nk * sizeof(int64_t)
+ (size_t)AGG_TS_SAMPLE_TASKS * sizeof(int64_t)
+ CAP * sizeof(uint64_t) + CAP * sizeof(uint16_t));
if (!hs) return (double)n_in;
uint16_t* cnt = (uint16_t*)(hs + set_bytes);
int64_t* keys = (int64_t*)(cnt + set_bytes);
int64_t* ns = keys + (size_t)AGG_TS_SAMPLE_TASKS * nk;
uint64_t* ghs = (uint64_t*)(ns + AGG_TS_SAMPLE_TASKS);
uint16_t* gcnt = (uint16_t*)(ghs + CAP);
agg_ts_sample_ctx_t ctx = {
.key_cols = key_cols, .key_data = key_data, .n_keys = n_keys,
.n_in = n_in, .step = n_in / AGG_TS_SAMPLE,
.sel = sel, .sel_prefix = sel_prefix,
.hs = hs, .cnt = cnt, .keys = keys, .n = ns,
};
if (ray_pool_par_dispatch_ok(pool, AGG_TS_SAMPLE, 1))
ray_pool_dispatch_n(pool, agg_ts_sample_fn, &ctx, AGG_TS_SAMPLE_TASKS);
else
agg_ts_sample_fn(&ctx, 0, 0, AGG_TS_SAMPLE_TASKS);
int64_t d = 0, n = 0;
for (int t = 0; t < AGG_TS_SAMPLE_TASKS; t++) {
n += ns[t];
const uint64_t* th = hs + (size_t)t * AGG_TS_SAMPLE_SETCAP;
const uint16_t* tc = cnt + (size_t)t * AGG_TS_SAMPLE_SETCAP;
for (int i = 0; i < AGG_TS_SAMPLE_SETCAP; i++) {
uint64_t h = th[i];
if (!h) continue;
uint64_t at = h & (CAP - 1);
while (ghs[at] && ghs[at] != h) at = (at + 1) & (CAP - 1);
if (!ghs[at]) { ghs[at] = h; d++; }
gcnt[at] = (uint16_t)(gcnt[at] + tc[i]);
}
}
int64_t f1 = 0, f2 = 0;
for (int64_t i = 0; i < CAP; i++) {
if (gcnt[i] == 1) f1++; else if (gcnt[i] == 2) f2++;
}
scratch_free(hdr);
if (n <= 0) return (double)n_in;
*unseen_pct = (int)(f1 * 100 / n);
if (d == n) return 9.2e18;
return (double)d + (f2 > 0 ? (double)f1 * (double)f1 / (2.0 * (double)f2)
: (double)f1 * (double)(f1 - 1) / 2.0);
}
static ray_t* exec_group_v2_parallel_tablestack(
ray_graph_t* g, ray_op_t* op, ray_t* tbl, int64_t nrows,
ray_t** key_cols, int64_t* key_syms,
const agg_vtable_t** vts, const size_t* off, size_t block,
ray_t* sel, const int64_t* sel_prefix, int64_t n_sel,
int64_t group_limit, const ray_group_emit_filter_t* efp) {
ray_op_ext_t* ext = find_ext(g, op->id);
uint32_t n_keys = ext->n_keys, n_aggs = ext->n_aggs;
ray_pool_t* pool = ray_pool_get();
uint32_t nw = ray_pool_total_workers(pool);
if (agg_ts_precheck_declines(key_cols, n_keys, nrows, sel, sel_prefix, n_sel)) {
ray_profile_tick("tablestack: gate declined");
return NULL;
}
agg_desc_t d;
if (!agg_desc_init(&d, g, ext, tbl, key_cols)) return ray_error("oom", NULL);
int64_t n_in = sel ? n_sel : nrows;
if (n_in < 0) n_in = 0;
uint64_t want = (uint64_t)n_in / AGG_TS_TASK_ROWS;
uint64_t max_tasks = nw > 1 ? (uint64_t)nw * AGG_TS_TASKS_PER_WORKER : 1;
if (want > max_tasks) want = max_tasks;
if (want < 1) want = 1;
uint32_t n_tasks = (uint32_t)want;
int64_t task_rows = (n_in + n_tasks - 1) / n_tasks;
if (task_rows < 1) task_rows = 1;
size_t state_off = AGG_TS_KEY_OFF + (size_t)n_keys * sizeof(int64_t);
size_t estride = (state_off + block + 7u) & ~(size_t)7u;
int unseen_pct = 0;
double est = agg_ts_estimate(pool, key_cols, d.key_data, n_keys, nrows,
sel, sel_prefix, n_sel, &unseen_pct);
uint32_t init_bits = AGG_TS_MIN_BITS;
while (init_bits < AGG_TS_MAX_BITS &&
(double)((int64_t)1 << init_bits) * 0.75 < est * 1.25) init_bits++;
uint64_t llc_bytes = ray_cache_llc_bytes();
double cache_budget = (llc_bytes ? (double)llc_bytes : 32.0 * 1048576.0) * 0.75;
double table_bytes = est * (double)estride + (double)((int64_t)1 << init_bits);
if (est > (double)AGG_TS_GATE_GROUPS || est * AGG_TS_GATE_REPEAT > (double)task_rows ||
unseen_pct > AGG_TS_GATE_UNSEEN_PCT ||
table_bytes * (double)nw > cache_budget) {
agg_desc_free(&d);
ray_profile_tick("tablestack: gate declined");
return NULL;
}
agg_route_record(AGG_ROUTE_V2_TABLESTACK);
ray_t* off_hdr;
size_t* val_off = (size_t*)scratch_calloc(&off_hdr,
3u * (size_t)(n_aggs ? n_aggs : 1) * sizeof(size_t));
if (!val_off) { agg_desc_free(&d); return ray_error("oom", NULL); }
size_t* val2_off = val_off + (n_aggs ? n_aggs : 1);
size_t* ent_off = val2_off + (n_aggs ? n_aggs : 1);
size_t rec_cur = (size_t)n_keys * 8;
for (uint32_t a = 0; a < n_aggs; a++) {
if (d.val_data[a]) { val_off[a] = rec_cur; rec_cur += d.val_esz[a]; }
if (d.val2_data[a]) { val2_off[a] = rec_cur; rec_cur += d.val2_esz[a]; }
}
size_t row_off = rec_cur; rec_cur += 8;
size_t rec = (rec_cur + 7u) & ~(size_t)7u;
for (uint32_t a = 0; a < n_aggs; a++) ent_off[a] = state_off + off[a];
uint32_t n_parts = 64;
while (n_parts < 8u * nw && n_parts < 1024u) n_parts <<= 1;
agg_ts_task_t* tasks = ray_calloc_raw((size_t)n_tasks * sizeof(agg_ts_task_t));
agg_radix_part_t* parts = ray_calloc_raw((size_t)n_parts * sizeof(agg_radix_part_t));
bool alloc_ok = tasks && parts;
for (uint32_t t = 0; alloc_ok && t < n_tasks; t++) {
tasks[t].top = agg_ts_table_new(init_bits, estride);
if (!tasks[t].top) alloc_ok = false; else tasks[t].n_tables = 1;
}
if (!alloc_ok) {
if (tasks) for (uint32_t t = 0; t < n_tasks; t++) agg_ts_table_free(tasks[t].top);
ray_free_raw(tasks); ray_free_raw(parts);
scratch_free(off_hdr); agg_desc_free(&d);
return exec_group_v2_parallel_smallhash(g, op, tbl, nrows,
key_cols, key_syms, vts, off, block, sel, sel_prefix, n_sel);
}
agg_ts_ctx_t ctx = {
.key_cols = key_cols, .key_data = d.key_data, .n_keys = n_keys,
.vts = vts, .off = off, .block = block, .n_aggs = n_aggs,
.val_data = d.val_data, .val_types = d.val_types, .val_hasnull = d.val_hasnull, .val_esz = d.val_esz,
.val2_data = d.val2_data, .val2_types = d.val2_types, .val2_hasnull = d.val2_hasnull, .val2_esz = d.val2_esz,
.vd = { .n_aggs = n_aggs, .vts = vts, .off = ent_off, .block = estride,
.val_data = d.val_data, .val_types = d.val_types, .val_hasnull = d.val_hasnull, .val_esz = d.val_esz,
.val2_data = d.val2_data, .val2_types = d.val2_types, .val2_hasnull = d.val2_hasnull, .val2_esz = d.val2_esz },
.ent_off = ent_off, .estride = estride, .state_off = state_off,
.rec = rec, .row_off = row_off, .val_off = val_off, .val2_off = val2_off,
.n_parts = n_parts, .part_bits = (uint32_t)__builtin_ctz(n_parts),
.init_bits = init_bits,
.n_in = n_in, .task_rows = task_rows, .n_tasks = n_tasks,
.tasks = tasks, .parts = parts,
.sel = sel, .sel_prefix = sel_prefix,
};
ray_pool_dispatch_n(pool, agg_ts_phase1_fn, &ctx, n_tasks);
bool cancelled = pool_cancelled(pool);
int oom = 0;
uint32_t scatter_tasks = 0; uint64_t tables = 0; int64_t cand = 0;
for (uint32_t t = 0; t < n_tasks; t++) {
if (tasks[t].oom) oom = 1;
if (tasks[t].scatter) scatter_tasks++;
tables += tasks[t].n_tables;
cand += tasks[t].entries + tasks[t].scattered;
}
ctx.merge_cand = cand;
route_stats.ts_tasks = n_tasks;
route_stats.ts_scatter_tasks = scatter_tasks;
route_stats.ts_tables = tables;
ray_profile_tick("tablestack: aggregated tasks");
if (!cancelled && !oom) {
ray_pool_dispatch_n(pool, agg_ts_merge_fn, &ctx, n_parts);
cancelled = pool_cancelled(pool);
for (uint32_t p = 0; p < n_parts; p++) if (parts[p].oom) { oom = 1; break; }
}
for (uint32_t t = 0; t < n_tasks; t++) agg_ts_task_free(&tasks[t], n_parts);
ray_free_raw(tasks);
scratch_free(off_hdr);
ray_profile_tick("tablestack: merged partitions");
if (cancelled || oom) {
agg_radix_parts_destroy(parts, n_parts, vts, off, block, n_aggs);
ray_free_raw(parts);
agg_desc_free(&d);
if (cancelled) return ray_error("cancel", NULL);
return exec_group_v2_parallel_smallhash(g, op, tbl, nrows,
key_cols, key_syms, vts, off, block, sel, sel_prefix, n_sel);
}
return agg_parts_emit(ext, &d, parts, n_parts, pool, nw, key_cols, key_syms,
vts, off, block, n_in, group_limit, efp);
}
static ray_t* agg_build_compact(ray_graph_t* g, ray_op_t* op, ray_t* tbl,
int64_t* idx, int64_t n_sel);
typedef struct {
const uint32_t* gids;
int64_t nrows;
uint32_t tasks, parts, bits, slices;
int64_t slots, local_stride;
int64_t* local;
const int64_t* offsets;
uint32_t part_slices[257];
int64_t slice_begin[512], slice_end[512];
int64_t* hist;
int64_t* starts;
uint64_t* packed;
int64_t* counts;
int64_t* cursor;
int64_t* rows;
} agg_index_layout_t;
static void agg_index_hist(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_layout_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t* hist = c->hist + task * c->parts;
int64_t begin = c->nrows / c->tasks * task;
int64_t limit = task + 1 == c->tasks ? c->nrows : c->nrows / c->tasks * (task + 1);
for (int64_t r = begin; r < limit; r++) hist[c->gids[r] >> c->bits]++;
}
}
static void agg_index_pack(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_layout_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t* cursor = c->hist + task * c->parts;
int64_t begin = c->nrows / c->tasks * task;
int64_t limit = task + 1 == c->tasks ? c->nrows : c->nrows / c->tasks * (task + 1);
for (int64_t r = begin; r < limit; r++)
c->packed[cursor[c->gids[r] >> c->bits]++] = ((uint64_t)c->gids[r] << 32) | (uint32_t)r;
}
}
static void agg_index_count(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_layout_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t* counts = c->local + task * c->local_stride;
for (int64_t i = c->slice_begin[task]; i < c->slice_end[task]; i++)
counts[(c->packed[i] >> 32) & (c->slots - 1)]++;
}
}
static void agg_index_merge_counts(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_layout_t* c = raw;
for (int64_t g = start; g < end; g++) {
uint32_t part = g >> c->bits;
int64_t count = 0;
for (uint32_t task = c->part_slices[part]; task < c->part_slices[part + 1]; task++)
count += c->local[task * c->local_stride + (g & (c->slots - 1))];
c->counts[g] = count;
}
}
static void agg_index_slice_cursors(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_layout_t* c = raw;
for (int64_t g = start; g < end; g++) {
uint32_t part = g >> c->bits;
int64_t offset = c->offsets[g];
for (uint32_t task = c->part_slices[part]; task < c->part_slices[part + 1]; task++) {
int64_t* slot = &c->local[task * c->local_stride + (g & (c->slots - 1))];
int64_t count = *slot; *slot = offset; offset += count;
}
}
}
static void agg_index_fill(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_layout_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t* cursor = c->local + task * c->local_stride;
for (int64_t i = c->slice_begin[task]; i < c->slice_end[task]; i++) {
uint64_t record = c->packed[i];
c->rows[cursor[(record >> 32) & (c->slots - 1)]++] = (uint32_t)record;
}
}
}
static void agg_index_direct_count(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_layout_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t* counts = c->local + task * c->slots;
int64_t begin = c->nrows / c->tasks * task;
int64_t limit = task + 1 == c->tasks ? c->nrows : c->nrows / c->tasks * (task + 1);
for (int64_t r = begin; r < limit; r++) counts[c->gids[r]]++;
}
}
static void agg_index_direct_fill(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_layout_t* c = raw;
for (int64_t task = start; task < end; task++) {
int64_t* cursor = c->local + task * c->slots;
int64_t begin = c->nrows / c->tasks * task;
int64_t limit = task + 1 == c->tasks ? c->nrows : c->nrows / c->tasks * (task + 1);
for (int64_t r = begin; r < limit; r++) c->rows[cursor[c->gids[r]]++] = r;
}
}
static int agg_index_layout(const agg_groups_t* groups, int64_t nrows,
int64_t* counts, int64_t* offsets, int64_t* cursor, int64_t* rows) {
ray_pool_t* pool = ray_pool_get();
int64_t ng = groups->ngroups;
uint32_t direct_tasks = pool ? ray_pool_total_workers(pool) * 4 : 0;
if (direct_tasks > RAY_POOL_INIT_TASKS) direct_tasks = RAY_POOL_INIT_TASKS;
int64_t direct_slots = ((ng + 7) & ~INT64_C(7)) + 8;
if (ray_pool_par_dispatch_ok(pool, nrows, RAY_PARALLEL_THRESHOLD) && direct_slots > 0 &&
direct_tasks && direct_slots <= 32768 / direct_tasks) {
agg_index_layout_t direct = {.gids = groups->gids, .nrows = nrows,
.tasks = direct_tasks, .slots = direct_slots, .rows = rows};
direct.local = ray_calloc_raw((size_t)direct_tasks * direct_slots * sizeof(int64_t));
if (!direct.local) return -1;
ray_pool_dispatch_n(pool, agg_index_direct_count, &direct, direct_tasks);
if (agg_cancelled()) { ray_free_raw(direct.local); return -1; }
for (int64_t g = 0; g < ng; g++) {
int64_t at = offsets[g];
for (uint32_t task = 0; task < direct_tasks; task++) {
int64_t* slot = direct.local + task * direct_slots + g;
int64_t count = *slot; *slot = at; at += count;
}
counts[g] = at - offsets[g]; offsets[g + 1] = at;
}
ray_pool_dispatch_n(pool, agg_index_direct_fill, &direct, direct_tasks);
ray_free_raw(direct.local);
return agg_cancelled() ? -1 : 0;
}
if (nrows > UINT32_MAX || !ray_pool_par_dispatch_ok(pool, nrows, RAY_PARALLEL_THRESHOLD) ||
ray_pool_total_workers(pool) < 4) {
for (int64_t r = 0; r < nrows; r++) counts[groups->gids[r]]++;
for (int64_t i = 0; i < ng; i++) offsets[i + 1] = offsets[i] + counts[i];
memcpy(cursor, offsets, (size_t)ng * sizeof(int64_t));
for (int64_t r = 0; r < nrows; r++) rows[cursor[groups->gids[r]]++] = r;
return 0;
}
uint32_t tasks = ray_pool_total_workers(pool);
if (tasks > RAY_POOL_INIT_TASKS) tasks = RAY_POOL_INIT_TASKS;
uint32_t parts = 1;
while (parts < tasks && parts < 256) parts *= 2;
uint32_t slots = 1;
while (slots < (ng + parts - 1) / parts) slots *= 2;
agg_index_layout_t c = {.gids = groups->gids, .nrows = nrows, .tasks = tasks,
.parts = parts, .bits = (uint32_t)__builtin_ctz(slots), .slots = slots, .local_stride = slots + 8,
.offsets = offsets, .counts = counts, .cursor = cursor, .rows = rows};
c.hist = ray_calloc_raw(((size_t)tasks * parts + parts + 1) * sizeof(int64_t));
c.packed = ray_alloc_raw((size_t)nrows * sizeof(uint64_t));
if (!c.hist || !c.packed) { ray_free_raw(c.hist); ray_free_raw(c.packed); return -1; }
c.starts = c.hist + (size_t)tasks * parts;
ray_pool_dispatch_n(pool, agg_index_hist, &c, tasks);
int64_t offset = 0;
for (uint32_t part = 0; part < parts; part++) {
c.starts[part] = offset;
for (uint32_t task = 0; task < tasks; task++) {
size_t at = (size_t)task * parts + part;
int64_t count = c.hist[at]; c.hist[at] = offset; offset += count;
}
}
c.starts[parts] = offset;
ray_profile_tick("indexed slices: histogram");
ray_pool_dispatch_n(pool, agg_index_pack, &c, tasks);
ray_profile_tick("indexed slices: packed");
int rc = -1;
if (agg_cancelled()) goto done;
int64_t grain = (nrows + parts - 1) / parts;
for (uint32_t part = 0; part < parts; part++) {
c.part_slices[part] = c.slices;
for (int64_t begin = c.starts[part]; begin < c.starts[part + 1];) {
int64_t end = c.starts[part + 1] - begin < grain ? c.starts[part + 1] : begin + grain;
c.slice_begin[c.slices] = begin; c.slice_end[c.slices++] = end;
begin = end;
}
}
c.part_slices[parts] = c.slices;
c.local = ray_calloc_raw((size_t)c.slices * c.local_stride * sizeof(int64_t));
if (!c.local) goto done;
ray_pool_dispatch_n(pool, agg_index_count, &c, c.slices);
ray_pool_dispatch(pool, agg_index_merge_counts, &c, ng);
if (agg_cancelled()) goto done;
ray_profile_tick("indexed slices: counted groups");
for (int64_t i = 0; i < ng; i++) offsets[i + 1] = offsets[i] + counts[i];
ray_pool_dispatch(pool, agg_index_slice_cursors, &c, ng);
ray_pool_dispatch_n(pool, agg_index_fill, &c, c.slices);
ray_profile_tick("indexed slices: filled");
if (!agg_cancelled()) rc = 0;
done:
ray_free_raw(c.local); ray_free_raw(c.hist); ray_free_raw(c.packed);
return rc;
}
typedef struct {
const agg_vtable_t* vt;
ray_t* x;
ray_t* y;
const int64_t* rows;
const int64_t* offsets;
int64_t ng, nrows;
uint32_t tasks;
char* states;
int64_t* partial_groups;
_Atomic(bool) failed;
} agg_index_stream_t;
static void agg_index_stream_init(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_stream_t* c = raw;
for (int64_t g = start; g < end; g++) c->vt->init(c->states + g * c->vt->state_size);
}
static void agg_index_stream_reduce(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_stream_t* c = raw;
uint8_t ex = c->x ? col_esz(c->x) : 0, ey = c->y ? col_esz(c->y) : 0;
char* x = ex ? ray_alloc_raw((size_t)AGG_SEL_CHUNK * ex) : NULL;
char* y = ey ? ray_alloc_raw((size_t)AGG_SEL_CHUNK * ey) : NULL;
if ((ex && !x) || (ey && !y)) { atomic_store(&c->failed, true); goto done; }
uint32_t gids[AGG_SEL_CHUNK] = {0};
ray_valid_t vx = {x, c->x ? c->x->type : RAY_I64, c->x && ray_vec_may_have_nulls(c->x)};
ray_valid_t vy = {y, c->y ? c->y->type : RAY_I64, c->y && ray_vec_may_have_nulls(c->y)};
for (int64_t task = start; task < end; task++) {
int64_t begin = c->nrows / c->tasks * task;
int64_t limit = task + 1 == c->tasks ? c->nrows : c->nrows / c->tasks * (task + 1);
int64_t lo = 0, hi = c->ng;
while (lo < hi) { int64_t mid = lo + (hi - lo) / 2; if (c->offsets[mid + 1] <= begin) lo = mid + 1; else hi = mid; }
int partial = 0;
for (int64_t group = lo, at = begin; at < limit && !agg_cancelled(); group++) {
int64_t stop = c->offsets[group + 1] < limit ? c->offsets[group + 1] : limit;
bool whole = at == c->offsets[group] && stop == c->offsets[group + 1];
int64_t slot = whole ? group : c->ng + 2 * task + partial;
char* state = c->states + slot * c->vt->state_size;
if (!whole) {
c->partial_groups[2 * task + partial++] = group;
c->vt->init(state);
}
while (at < stop) {
int64_t n = stop - at < AGG_SEL_CHUNK ? stop - at : AGG_SEL_CHUNK;
if (ex) agg_sel_gather_vals(x, ray_data(c->x), ex, c->rows + at, n);
if (ey) agg_sel_gather_vals(y, ray_data(c->y), ey, c->rows + at, n);
if (c->y) c->vt->update_batch2(state, c->vt->state_size, gids, x, y, &vx, &vy, n, NULL);
else c->vt->update_batch(state, c->vt->state_size, gids, x, &vx, n, NULL);
at += n;
}
}
}
done:
ray_free_raw(x); ray_free_raw(y);
}
static ray_t* agg_index_streaming(const agg_vtable_t* vt, ray_t* x, ray_t* y,
const agg_groups_t* groups, const int64_t* rows, const int64_t* offsets,
int64_t nrows, int64_t param) {
ray_pool_t* pool = ray_pool_get();
if (!ray_pool_par_dispatch_ok(pool, nrows, RAY_PARALLEL_THRESHOLD))
return y ? agg_run_one_bin(vt, x, y, groups->gids, nrows, groups->ngroups, param)
: agg_run_one(vt, x, groups->gids, nrows, groups->ngroups, param);
uint32_t tasks = ray_pool_total_workers(pool) * 4;
if (tasks > RAY_POOL_INIT_TASKS) tasks = RAY_POOL_INIT_TASKS;
agg_index_stream_t c = {.vt = vt, .x = x, .y = y, .rows = rows, .offsets = offsets,
.ng = groups->ngroups, .nrows = nrows, .tasks = tasks, .failed = false};
c.states = ray_alloc_raw((size_t)(c.ng + 2 * tasks) * vt->state_size);
c.partial_groups = ray_alloc_raw((size_t)2 * tasks * sizeof(int64_t));
ray_t* result = NULL;
if (!c.states || !c.partial_groups) goto done;
for (uint32_t t = 0; t < 2 * tasks; t++) c.partial_groups[t] = -1;
ray_pool_dispatch(pool, agg_index_stream_init, &c, c.ng);
ray_pool_dispatch_n(pool, agg_index_stream_reduce, &c, tasks);
if (agg_cancelled() || atomic_load(&c.failed)) goto done;
for (uint32_t t = 0; t < 2 * tasks; t++) if (c.partial_groups[t] >= 0)
vt->merge(c.states + c.partial_groups[t] * vt->state_size,
c.states + (c.ng + t) * vt->state_size, NULL);
result = ray_vec_new(vt->out_type, c.ng);
if (!result || RAY_IS_ERR(result)) goto done;
result->len = c.ng;
agg_dense_emit_ctx_t emit = {.vt = vt, .out = result, .states = c.states,
.block = vt->state_size, .param = param, .any_null = false};
ray_pool_dispatch(pool, agg_dense_emit_fn, &emit, c.ng);
if (atomic_load(&emit.any_null)) result->attrs |= RAY_ATTR_HAS_NULLS;
done:
ray_free_raw(c.states); ray_free_raw(c.partial_groups);
if (agg_cancelled()) { ray_release(result); return ray_error("cancel", NULL); }
return result ? result : ray_error("oom", NULL);
}
typedef struct {
ray_t* src;
uint16_t kind;
ray_group_sym_view_t symbols;
} agg_index_winners_t;
static int64_t agg_index_winner(void* raw, const int64_t* rows, int64_t count) {
agg_index_winners_t* c = raw;
const void* data = ray_data(c->src);
if (c->kind == OP_FIRST || c->kind == OP_LAST) {
bool last = c->kind == OP_LAST;
if (!count || ray_interrupted()) return -1;
if (!ray_vec_may_have_nulls(c->src) && c->src->type != RAY_SYM && c->src->type != RAY_STR)
return rows[last ? count - 1 : 0];
#define INDEX_FIRST_VALID(TYPE, VALID) do { \
const TYPE* values = data; \
for (int64_t j = 0; j < count; j++) { \
if ((j & 65535) == 0 && ray_interrupted()) return -1; \
int64_t row = rows[last ? count - 1 - j : j]; \
TYPE value = values[row]; \
if (VALID) return row; \
} \
return -1; \
} while (0)
switch (c->src->type) {
case RAY_I16: INDEX_FIRST_VALID(int16_t, value != NULL_I16);
case RAY_I32: case RAY_DATE: case RAY_TIME:
INDEX_FIRST_VALID(int32_t, value != NULL_I32);
case RAY_I64: case RAY_TIMESTAMP:
INDEX_FIRST_VALID(int64_t, value != NULL_I64);
case RAY_F32: INDEX_FIRST_VALID(float, value == value);
case RAY_F64: INDEX_FIRST_VALID(double, value == value);
default: break;
}
#undef INDEX_FIRST_VALID
}
int64_t best = -1;
struct ray_sym_domain_s* dom = ray_sym_vec_domain(c->src);
ray_sym_domain_raw_t vocab;
bool vocab_ok = ray_sym_domain_raw_pin(dom, &vocab);
for (int64_t j = 0; j < count; j++) {
if ((j & 65535) == 0 && ray_interrupted()) return -1;
int64_t r = rows[c->kind == OP_LAST ? count - 1 - j : j];
if (ray_vec_is_null(c->src, r)) continue;
if (best < 0) best = r;
if (c->kind == OP_FIRST || c->kind == OP_LAST) break;
int64_t ir = ray_read_sym(data, r, c->src->type, c->src->attrs);
int64_t ib = ray_read_sym(data, best, c->src->type, c->src->attrs);
int cmp;
if (vocab_ok && ir >= 0 && ib >= 0 && ir < vocab.count && ib < vocab.count) {
size_t lr, lb;
const char* pr = ray_sym_domain_raw_str(&vocab, ir, &lr);
const char* pb = ray_sym_domain_raw_str(&vocab, ib, &lb);
size_t m = lr < lb ? lr : lb;
cmp = m ? memcmp(pr, pb, m) : 0;
if (cmp == 0) cmp = (lr > lb) - (lr < lb);
} else {
ray_t* x = ray_group_sym_read(&c->symbols, dom, ir);
ray_t* y = ray_group_sym_read(&c->symbols, dom, ib);
cmp = ray_str_cmp(x, y);
}
if (c->kind == OP_MIN ? cmp < 0 : cmp > 0) best = r;
}
return best;
}
typedef struct {
const int64_t* rows;
const int64_t* offsets;
const int64_t* counts;
ray_t** values;
_Atomic(bool) failed;
} agg_index_vectors_t;
static void agg_index_vectors_run(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_index_vectors_t* c = raw;
for (int64_t g = start; g < end && !agg_cancelled(); g++) {
ray_t* value = ray_vec_new(RAY_I64, c->counts[g]);
if (!value || RAY_IS_ERR(value)) { ray_release(value); atomic_store(&c->failed, true); return; }
value->len = c->counts[g];
memcpy(ray_data(value), c->rows + c->offsets[g], (size_t)value->len * sizeof(int64_t));
c->values[g] = value;
}
}
ray_t* agg_group_indices(ray_t* source) {
ray_pool_t* pool = ray_pool_get();
int64_t n = source->len;
if (n > INT32_MAX || !ray_pool_par_dispatch_ok(pool, n, RAY_PARALLEL_THRESHOLD) ||
ray_pool_total_workers(pool) < 4) return NULL;
switch (source->type) {
case RAY_BOOL: case RAY_U8: case RAY_I16: case RAY_I32: case RAY_I64:
case RAY_F32: case RAY_F64: case RAY_DATE: case RAY_TIME: case RAY_TIMESTAMP:
case RAY_SYM: case RAY_GUID: case RAY_STR: case RAY_LIST: break;
default: return NULL;
}
agg_groups_t groups = {0};
dense_plan_t plan;
bool dense = agg_dense_plan(&source, 1, NULL, 0, n, &plan);
int rc = dense ? agg_group_keys_dense(&source, n, &plan, &groups)
: agg_group_keys(&source, 1, n, &groups);
if (rc) return ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
int64_t ng = groups.ngroups;
int64_t* scratch = ray_calloc_raw((size_t)(n + 3 * ng + 1) * sizeof(int64_t));
if (!scratch) { agg_groups_free(&groups); return ray_error("oom", NULL); }
int64_t* offsets = scratch + ng;
int64_t* cursor = offsets + ng + 1;
int64_t* rows = cursor + ng;
ray_t* keys = NULL;
ray_t* values = NULL;
ray_t* result = NULL;
if (agg_index_layout(&groups, n, scratch, offsets, cursor, rows)) goto failed;
keys = ray_group_gather(source, groups.first_row, ng);
if (!keys || RAY_IS_ERR(keys)) goto failed;
values = ray_list_new(ng);
if (!values || RAY_IS_ERR(values)) goto failed;
memset(ray_data(values), 0, (size_t)ng * sizeof(ray_t*));
values->len = ng;
agg_index_vectors_t emit = {rows, offsets, scratch, ray_data(values), false};
ray_group_dispatch(agg_index_vectors_run, &emit, scratch, ng);
if (agg_cancelled() || atomic_load(&emit.failed)) goto failed;
result = ray_dict_new(keys, values);
keys = values = NULL;
failed:
ray_release(keys); ray_release(values); ray_free_raw(scratch); agg_groups_free(&groups);
return result ? result : ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
static ray_t* agg_indexed_run(ray_graph_t* g, ray_op_t* op, ray_t* tbl,
ray_t** keys, int64_t* key_syms, int64_t nrows) {
ray_op_ext_t* ext = find_ext(g, op->id);
agg_groups_t groups = {0};
dense_plan_t dp;
bool dense = agg_dense_plan(keys, ext->n_keys, NULL, 0, nrows, &dp);
route_stats.dense_plan_available = dense;
int rc = dense ? agg_group_keys_dense(keys, nrows, &dp, &groups)
: agg_group_keys(keys, ext->n_keys, nrows, &groups);
agg_dense_plan_free(&dp);
if (rc) return ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
if (agg_cancelled()) { agg_groups_free(&groups); return ray_error("cancel", NULL); }
int64_t ng = groups.ngroups;
int64_t* counts = ray_calloc_raw((size_t)(ng + 1) * sizeof(int64_t));
int64_t* offsets = ray_calloc_raw((size_t)(ng + 1) * sizeof(int64_t));
int64_t* cursor = ray_calloc_raw((size_t)(ng + 1) * sizeof(int64_t));
int64_t* rows = ray_alloc_raw((size_t)(nrows + 1) * sizeof(int64_t));
ray_t* result = NULL;
if (!counts || !offsets || !cursor || !rows) { result = ray_error("oom", NULL); goto done; }
ray_profile_tick("indexed: grouped keys");
if (agg_index_layout(&groups, nrows, counts, offsets, cursor, rows)) {
result = ray_error(agg_cancelled() ? "cancel" : "oom", NULL); goto done;
}
ray_profile_tick("indexed: stable row slices");
result = ray_table_new(ext->n_keys + ext->n_aggs);
if (!result || RAY_IS_ERR(result)) goto done;
for (uint32_t k = 0; k < ext->n_keys; k++) {
ray_t* col = ray_group_gather(keys[k], groups.first_row, ng);
if (!col || RAY_IS_ERR(col)) { ray_release(result); result = col; goto done; }
if (col->type == RAY_F32 || col->type == RAY_F64) {
for (int64_t i = 0; i < ng; i++) {
if (col->type == RAY_F32) {
float* d = ray_data(col); if (d[i] == 0) memset(&d[i], 0, sizeof(float));
} else {
double* d = ray_data(col); if (d[i] == 0) memset(&d[i], 0, sizeof(double));
}
}
}
result = ray_table_add_col(result, key_syms[k], col); ray_release(col);
if (!result || RAY_IS_ERR(result)) goto done;
}
ray_profile_tick("indexed: emitted keys");
for (uint32_t a = 0; a < ext->n_aggs; a++) {
if (agg_cancelled()) { ray_release(result); result = ray_error("cancel", NULL); goto done; }
uint16_t kind = ext->agg_ops[a];
ray_op_ext_t* ie = find_ext(g, ext->agg_ins[a]);
ray_t* src = ie ? ray_table_get_col(tbl, ie->sym) : NULL;
int64_t param = ext->agg_k ? ext->agg_k[a] : 0;
ray_t* col = NULL;
if (kind == OP_MEDIAN) col = ray_median_per_group_buf(src, rows, offsets, counts, ng);
else if (kind == OP_QUANTILE) {
double q = 0.5; if (ext->agg_k) memcpy(&q, ¶m, sizeof(q));
col = ray_quantile_per_group_buf(src, rows, offsets, counts, ng, q);
} else if (kind == OP_MODE) col = ray_mode_per_group_buf(src, rows, offsets, counts, ng);
else if (kind == OP_TOP_N || kind == OP_BOT_N)
col = ray_topk_per_group_buf(src, param, kind == OP_TOP_N, rows, offsets, counts, ng);
else if (agg_indexed_supported(kind, src ? src->type : RAY_I64)) {
col = ray_wide_minmax_per_group_buf(src, kind, rows, offsets, counts, ng);
if (!col) {
int64_t* winners = cursor;
agg_index_winners_t work = {src, kind, {0}};
if (src->type == RAY_SYM)
ray_sym_strings_borrow(&work.symbols.strings, &work.symbols.count);
ray_group_winners(agg_index_winner, &work, rows, offsets, counts, ng, winners);
if (agg_cancelled()) { ray_release(result); result = ray_error("cancel", NULL); goto done; }
ray_profile_tick("indexed: selected winning rows");
col = ray_group_gather(src, winners, ng);
if (col && !RAY_IS_ERR(col) && src->type == RAY_F32) {
ray_t* widened = ray_vec_new(RAY_F64, ng);
if (widened && !RAY_IS_ERR(widened)) {
widened->len = ng;
for (int64_t i = 0; i < ng; i++) ((double*)ray_data(widened))[i] = ((float*)ray_data(col))[i];
widened->attrs |= col->attrs & RAY_ATTR_HAS_NULLS;
}
ray_release(col); col = widened;
}
}
} else if (ext->agg_ins2 && ext->agg_ins2[a] != RAY_OP_NONE) {
ray_op_ext_t* ye = find_ext(g, ext->agg_ins2[a]);
col = agg_index_streaming(agg_resolve(kind, src->type), src,
ray_table_get_col(tbl, ye->sym), &groups, rows, offsets, nrows, param);
} else if (kind == OP_COUNT) {
col = ray_vec_new(RAY_I64, ng);
if (col && !RAY_IS_ERR(col)) { col->len = ng; memcpy(ray_data(col), counts, (size_t)ng * sizeof(int64_t)); }
} else col = agg_index_streaming(agg_resolve(kind, src ? src->type : RAY_I64),
src, NULL, &groups, rows, offsets, nrows, param);
if (!col || RAY_IS_ERR(col)) { ray_release(result); result = col; goto done; }
ray_profile_tick("indexed: emitted aggregate");
result = ray_table_add_col(result, agg_result_col_name(ie ? ie->sym : 0, kind), col);
ray_release(col);
if (!result || RAY_IS_ERR(result)) goto done;
}
done:
if (result && !RAY_IS_ERR(result) && agg_cancelled()) { ray_release(result); result = ray_error("cancel", NULL); }
ray_free_raw(counts); ray_free_raw(offsets); ray_free_raw(cursor); ray_free_raw(rows);
agg_groups_free(&groups);
return result ? result : ray_error("oom", NULL);
}
static bool agg_shared_sample(ray_graph_t* g, ray_op_ext_t* ext, ray_t* tbl,
ray_t** keys, const agg_vo_t* vo, int64_t rows) {
if (!ext->n_aggs) return true;
if (rows < 1024) return false;
enum { SAMPLES = 1024, CAP = 2048 };
agg_desc_t d;
if (!agg_desc_init(&d, g, ext, tbl, keys)) return false;
char* states = ray_alloc_raw((SAMPLES + 1) * vo->block);
if (!states) { agg_desc_free(&d); return false; }
char* previous = states + SAMPLES * vo->block;
int16_t map[CAP]; int64_t values[SAMPLES];
memset(map, -1, sizeof(map));
uint32_t gid = 0;
int groups = 0, unchanged = 0;
int64_t step = rows / SAMPLES;
for (int i = 0; i < SAMPLES; i++) {
uint64_t jitter = ray_hash_i64(i);
int64_t row = i * step + jitter % step;
int64_t value = agg_read_key_i64(keys[0], ray_data(keys[0]), row);
uint32_t at = ray_hash_i64(value) & (CAP - 1);
while (map[at] >= 0 && values[map[at]] != value) at = (at + 1) & (CAP - 1);
if (map[at] < 0) {
map[at] = groups; values[groups] = value;
for (uint32_t a = 0; a < ext->n_aggs; a++) vo->vts[a]->init(states + groups * vo->block + vo->off[a]);
groups++;
}
char* state = states + map[at] * vo->block;
memcpy(previous, state, vo->block);
for (uint32_t a = 0; a < ext->n_aggs; a++) {
const void* input = d.val_data[a] ? (const char*)d.val_data[a] + (size_t)row * d.val_esz[a] : NULL;
ray_valid_t valid = {input, d.val_types[a], d.val_hasnull[a]};
vo->vts[a]->update_shared(state + vo->off[a], vo->block, &gid, input, &valid, 1);
}
unchanged += memcmp(previous, state, vo->block) == 0;
}
ray_free_raw(states); agg_desc_free(&d);
return unchanged >= SAMPLES / 4;
}
static ray_t* exec_group_v2_run_inner(ray_graph_t* g, ray_op_t* op, ray_t* tbl,
int64_t nrows, ray_t* sel,
const int64_t* sel_prefix, int64_t n_sel,
int64_t group_limit,
const ray_group_emit_filter_t* efp);
ray_t* agg_emit_filter_trim(ray_t* result, uint32_t n_keys, uint32_t n_aggs,
const uint16_t* agg_ops, const ray_group_emit_filter_t* ef) {
if (!result || RAY_IS_ERR(result) || result->type != RAY_TABLE) return result;
if (ef->agg_index >= n_aggs) return result;
if (agg_ops && (ef->agg_op ? ef->agg_op : OP_COUNT) != agg_ops[ef->agg_index]) return result;
int64_t nrows = ray_table_nrows(result);
if (nrows <= 0) return result;
ray_t* vcol = ray_table_get_col_idx(result, (int64_t)n_keys + ef->agg_index);
if (!vcol || (vcol->type != RAY_I64 && vcol->type != RAY_F64)) return result;
double* vals = ray_alloc_raw((size_t)nrows * sizeof(double));
uint8_t* keep = ray_alloc_raw((size_t)nrows);
ray_t* idx = ray_vec_new(RAY_I64, nrows);
if (!vals || !keep || !idx || RAY_IS_ERR(idx)) {
ray_free_raw(vals); ray_free_raw(keep);
if (idx && RAY_IS_ERR(idx)) ray_error_free(idx); else ray_release(idx);
return result;
}
if (vcol->type == RAY_F64) {
const double* vf = (const double*)ray_data(vcol);
for (int64_t r = 0; r < nrows; r++) vals[r] = vf[r] != vf[r] ? -INFINITY : vf[r];
} else {
const int64_t* vi = (const int64_t*)ray_data(vcol);
for (int64_t r = 0; r < nrows; r++) vals[r] = vi[r] == NULL_I64 ? -INFINITY : (double)vi[r];
}
int64_t kept = agg_topn_keep(vals, nrows, ef, keep);
int64_t* ix = (int64_t*)ray_data(idx);
int64_t w = 0;
for (int64_t r = 0; r < nrows; r++) if (keep[r]) ix[w++] = r;
idx->len = w;
ray_free_raw(vals); ray_free_raw(keep);
if (kept == nrows) { ray_release(idx); return result; }
ray_t* out = ray_at_fn(result, idx);
ray_release(idx);
if (!out || RAY_IS_ERR(out)) { if (out) ray_error_free(out); return result; }
ray_release(result);
return out;
}
static bool agg_emit_filter_targets(const ray_group_emit_filter_t* ef, const ray_op_ext_t* ext) {
return ef && ef->enabled && ext && ef->agg_index < ext->n_aggs &&
(ef->agg_op ? ef->agg_op : OP_COUNT) == ext->agg_ops[ef->agg_index];
}
static ray_t* exec_group_v2_run(ray_graph_t* g, ray_op_t* op, ray_t* tbl,
int64_t nrows, ray_t* sel,
const int64_t* sel_prefix, int64_t n_sel,
int64_t group_limit,
const ray_group_emit_filter_t* efp) {
ray_op_ext_t* ext = find_ext(g, op->id);
if (!agg_emit_filter_targets(efp, ext)) efp = NULL;
ray_t* r = exec_group_v2_run_inner(g, op, tbl, nrows, sel, sel_prefix, n_sel,
group_limit, efp);
if (efp && r && !RAY_IS_ERR(r) && !route_stats.topn_native)
r = agg_emit_filter_trim(r, ext->n_keys, ext->n_aggs, ext->agg_ops, efp);
return r;
}
#define AGG_FIRSTN_MAX 1024
#define AGG_FIRSTN_PROBE_ROWS 65536
#define AGG_FIRSTN_SAMPLES 4096
#define AGG_FIRSTN_SHARE 64
bool ray_agg_first_n = true;
static _Thread_local bool firstn_nested;
typedef struct {
int64_t* keys;
int32_t* rank;
uint64_t* bloom;
uint32_t n_keys;
uint32_t bits;
uint32_t bbits;
int64_t n;
} agg_firstn_set_t;
#define AGG_FIRSTN_SEED 0x9E3779B97F4A7C15ULL
#define AGG_FIRSTN_MIX(h, v) \
do { (h) = ((h) ^ (uint64_t)(v)) * 0x9E3779B97F4A7C15ULL; (h) ^= (h) >> 32; } while (0)
#define AGG_FIRSTN_FINAL(h) ((h) * 0xD6E8FEB86659FD93ULL)
static inline uint64_t agg_firstn_hash(const int64_t* kv, uint32_t n_keys) {
uint64_t h = AGG_FIRSTN_SEED;
for (uint32_t k = 0; k < n_keys; k++) AGG_FIRSTN_MIX(h, kv[k]);
return AGG_FIRSTN_FINAL(h);
}
static inline int64_t agg_firstn_probe(agg_firstn_set_t* s, const int64_t* kv,
uint64_t hv, bool insert) {
uint64_t mask = ((uint64_t)1 << s->bits) - 1;
for (uint64_t i = hv >> (64 - s->bits);; i = (i + 1) & mask) {
int32_t r = s->rank[i];
int64_t* sk = s->keys + i * s->n_keys;
if (r < 0) {
if (!insert) return -1;
for (uint32_t k = 0; k < s->n_keys; k++) sk[k] = kv[k];
s->rank[i] = (int32_t)s->n;
s->n++;
return -1;
}
uint32_t k = 0;
while (k < s->n_keys && sk[k] == kv[k]) k++;
if (k == s->n_keys) return r;
}
}
static void agg_firstn_hash_block(uint64_t* h, ray_t* col, const void* data,
int64_t b, int64_t n) {
switch (col->type) {
case RAY_I64: case RAY_TIMESTAMP: {
const int64_t* d = (const int64_t*)data + b;
for (int64_t i = 0; i < n; i++) AGG_FIRSTN_MIX(h[i], d[i]);
break;
}
case RAY_I32: case RAY_DATE: case RAY_TIME: {
const int32_t* d = (const int32_t*)data + b;
for (int64_t i = 0; i < n; i++) AGG_FIRSTN_MIX(h[i], (int64_t)d[i]);
break;
}
case RAY_I16: {
const int16_t* d = (const int16_t*)data + b;
for (int64_t i = 0; i < n; i++) AGG_FIRSTN_MIX(h[i], (int64_t)d[i]);
break;
}
case RAY_U8: case RAY_BOOL: {
const uint8_t* d = (const uint8_t*)data + b;
for (int64_t i = 0; i < n; i++) AGG_FIRSTN_MIX(h[i], (int64_t)d[i]);
break;
}
case RAY_SYM:
switch (col->attrs & RAY_SYM_W_MASK) {
case RAY_SYM_W8: {
const uint8_t* d = (const uint8_t*)data + b;
for (int64_t i = 0; i < n; i++) AGG_FIRSTN_MIX(h[i], (int64_t)d[i]);
break;
}
case RAY_SYM_W16: {
const uint16_t* d = (const uint16_t*)data + b;
for (int64_t i = 0; i < n; i++) AGG_FIRSTN_MIX(h[i], (int64_t)d[i]);
break;
}
case RAY_SYM_W32: {
const uint32_t* d = (const uint32_t*)data + b;
for (int64_t i = 0; i < n; i++) AGG_FIRSTN_MIX(h[i], (int64_t)d[i]);
break;
}
default: {
const int64_t* d = (const int64_t*)data + b;
for (int64_t i = 0; i < n; i++) AGG_FIRSTN_MIX(h[i], d[i]);
break;
}
}
break;
default:
for (int64_t i = 0; i < n; i++) AGG_FIRSTN_MIX(h[i], agg_read_key_i64(col, data, b + i));
break;
}
}
#define AGG_FIRSTN_LOCAL 1024
typedef struct {
agg_firstn_set_t* set;
ray_t** key_cols;
const void** key_data;
uint32_t n_keys;
int64_t nrows;
ray_t* sel;
uint32_t n_segs, tasks;
int64_t* counts;
int64_t* local;
int64_t* out;
int64_t* kvs;
} agg_firstn_ctx_t;
static void agg_firstn_task(agg_firstn_ctx_t* c, uint32_t t) {
int64_t s0 = (int64_t)c->n_segs * t / c->tasks;
int64_t s1 = (int64_t)c->n_segs * (t + 1) / c->tasks;
agg_firstn_set_t* set = c->set;
uint32_t n_keys = c->n_keys;
uint64_t hb[RAY_MORSEL_ELEMS];
int64_t* kv = c->kvs + (int64_t)t * n_keys;
const uint8_t* fl = c->sel ? ray_rowsel_flags(c->sel) : NULL;
const uint32_t* of = c->sel ? ray_rowsel_offsets(c->sel) : NULL;
const uint16_t* ix = c->sel ? ray_rowsel_idx(c->sel) : NULL;
int64_t* out = c->out ? c->out + c->counts[t] : c->local + (int64_t)t * AGG_FIRSTN_LOCAL;
int64_t out_cap = c->out ? INT64_MAX : AGG_FIRSTN_LOCAL;
uint32_t bshift = 64 - set->bbits;
int64_t n = 0;
for (int64_t seg = s0; seg < s1; seg++) {
if (fl && fl[seg] == RAY_SEL_NONE) continue;
int64_t b = seg * RAY_MORSEL_ELEMS;
int64_t len = c->nrows - b < RAY_MORSEL_ELEMS ? c->nrows - b : RAY_MORSEL_ELEMS;
for (int64_t i = 0; i < len; i++) hb[i] = AGG_FIRSTN_SEED;
for (uint32_t k = 0; k < n_keys; k++)
agg_firstn_hash_block(hb, c->key_cols[k], c->key_data[k], b, len);
bool mix = fl && fl[seg] == RAY_SEL_MIX;
int64_t j_end = mix ? (int64_t)of[seg + 1] : len;
for (int64_t j = mix ? (int64_t)of[seg] : 0; j < j_end; j++) {
int64_t i = mix ? (int64_t)ix[j] : j;
uint64_t hv = AGG_FIRSTN_FINAL(hb[i]);
uint64_t bit = hv >> bshift;
if (!((set->bloom[bit >> 6] >> (bit & 63)) & 1)) continue;
for (uint32_t k = 0; k < n_keys; k++)
kv[k] = agg_read_key_i64(c->key_cols[k], c->key_data[k], b + i);
if (agg_firstn_probe(set, kv, hv, false) < 0) continue;
if (n < out_cap) out[n] = b + i;
n++;
}
}
if (!c->out) c->counts[t] = n;
}
static void agg_firstn_fn(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid;
for (int64_t t = start; t < end; t++) agg_firstn_task((agg_firstn_ctx_t*)raw, (uint32_t)t);
}
static bool agg_firstn_admitted(ray_graph_t* g, ray_op_ext_t* ext, ray_t* tbl,
ray_t** key_cols) {
for (uint32_t k = 0; k < ext->n_keys; k++)
switch (key_cols[k]->type) {
case RAY_I64: case RAY_I32: case RAY_I16: case RAY_U8:
case RAY_BOOL: case RAY_DATE: case RAY_TIME:
case RAY_TIMESTAMP: case RAY_SYM: break;
default: return false;
}
for (uint32_t a = 0; a < ext->n_aggs; a++) {
uint16_t aop = ext->agg_ops[a];
if (aop != OP_COUNT && aop != OP_SUM && aop != OP_AVG && aop != OP_MIN && aop != OP_MAX)
return false;
if (ext->agg_ins2 && ext->agg_ins2[a] != RAY_OP_NONE) return false;
bool has_in = ext->agg_ins && ext->agg_ins[a] != RAY_OP_NONE;
if (!has_in) { if (aop == OP_COUNT) continue; return false; }
ray_op_ext_t* ie = find_ext(g, ext->agg_ins[a]);
if (!ie || ie->base.opcode != OP_SCAN) return false;
ray_t* col = ray_table_get_col(tbl, ie->sym);
if (!col) return false;
if (aop == OP_COUNT) continue;
switch (col->type) {
case RAY_I64: case RAY_I32: case RAY_I16: case RAY_U8:
case RAY_BOOL: case RAY_DATE: case RAY_TIME: case RAY_TIMESTAMP: break;
default: return false;
}
}
return true;
}
static ray_t* agg_first_n_run(ray_graph_t* g, ray_op_t* op, ray_t* tbl, int64_t nrows,
ray_t** key_cols, ray_t* sel, int64_t n_sel,
int64_t group_limit) {
ray_op_ext_t* ext = find_ext(g, op->id);
ray_pool_t* pool = ray_pool_get();
int64_t eff_n = sel ? n_sel : nrows;
if (!ray_agg_first_n || firstn_nested || !pool || eff_n < RAY_PARALLEL_THRESHOLD ||
group_limit <= 0 || group_limit > AGG_FIRSTN_MAX || !ext)
return NULL;
if (sel && ray_rowsel_meta(sel)->nrows != nrows) return NULL;
if (!agg_firstn_admitted(g, ext, tbl, key_cols)) return NULL;
uint32_t n_keys = ext->n_keys;
uint32_t bits = 4;
while (((int64_t)1 << bits) < group_limit * 4) bits++;
uint32_t bbits = bits + 6 > 16 ? bits + 6 : 16;
int64_t cap = (int64_t)1 << bits;
ray_t* res = NULL;
ray_t* kv_hdr = NULL;
int64_t* kv = (int64_t*)scratch_alloc(&kv_hdr, (size_t)n_keys * 2 * sizeof(int64_t));
agg_firstn_set_t set = { .n_keys = n_keys, .bits = bits, .bbits = bbits, .n = 0 };
set.keys = ray_alloc_raw((size_t)cap * n_keys * sizeof(int64_t));
set.rank = ray_alloc_raw((size_t)cap * sizeof(int32_t));
set.bloom = ray_calloc_raw(((size_t)1 << bbits) / 8);
int64_t* counts = NULL;
int64_t* local = NULL;
int64_t* kvs = NULL;
int64_t* idx = NULL;
int64_t* perm = NULL;
ray_t* compact = NULL;
ray_t* sub = NULL;
if (!kv || !set.keys || !set.rank || !set.bloom) { res = ray_error("oom", NULL); goto done; }
memset(set.rank, 0xFF, (size_t)cap * sizeof(int32_t));
const void** key_data = (const void**)(kv + n_keys);
for (uint32_t k = 0; k < n_keys; k++) key_data[k] = ray_data(key_cols[k]);
uint32_t n_segs = (uint32_t)((nrows + RAY_MORSEL_ELEMS - 1) / RAY_MORSEL_ELEMS);
{
const uint8_t* fl = sel ? ray_rowsel_flags(sel) : NULL;
const uint32_t* of = sel ? ray_rowsel_offsets(sel) : NULL;
const uint16_t* ix = sel ? ray_rowsel_idx(sel) : NULL;
int64_t budget = group_limit * 64 > AGG_FIRSTN_PROBE_ROWS
? group_limit * 64 : AGG_FIRSTN_PROBE_ROWS;
int64_t scanned = 0;
for (int64_t seg = 0; seg < n_segs && set.n < group_limit && scanned < budget; seg++) {
if (fl && fl[seg] == RAY_SEL_NONE) continue;
int64_t b = seg * RAY_MORSEL_ELEMS;
int64_t len = nrows - b < RAY_MORSEL_ELEMS ? nrows - b : RAY_MORSEL_ELEMS;
bool mix = fl && fl[seg] == RAY_SEL_MIX;
int64_t j_end = mix ? (int64_t)of[seg + 1] : len;
for (int64_t j = mix ? (int64_t)of[seg] : 0;
j < j_end && set.n < group_limit && scanned < budget; j++, scanned++) {
int64_t row = b + (mix ? (int64_t)ix[j] : j);
for (uint32_t k = 0; k < n_keys; k++)
kv[k] = agg_read_key_i64(key_cols[k], key_data[k], row);
agg_firstn_probe(&set, kv, agg_firstn_hash(kv, n_keys), true);
}
}
if (set.n < group_limit) goto done;
}
for (int64_t i = 0; i < cap; i++) {
if (set.rank[i] < 0) continue;
uint64_t bit = agg_firstn_hash(set.keys + i * n_keys, n_keys) >> (64 - bbits);
set.bloom[bit >> 6] |= (uint64_t)1 << (bit & 63);
}
{
int64_t samples = nrows < AGG_FIRSTN_SAMPLES ? nrows : AGG_FIRSTN_SAMPLES;
int64_t step = nrows / samples, hits = 0;
for (int64_t i = 0; i < samples; i++) {
for (uint32_t k = 0; k < n_keys; k++)
kv[k] = agg_read_key_i64(key_cols[k], key_data[k], i * step);
if (agg_firstn_probe(&set, kv, agg_firstn_hash(kv, n_keys), false) >= 0) hits++;
}
if (hits * AGG_FIRSTN_SHARE > samples) goto done;
}
uint32_t tasks = 1;
if (ray_pool_par_dispatch_ok(pool, eff_n, RAY_PARALLEL_THRESHOLD)) {
uint32_t want = ray_pool_total_workers(pool) * 4;
if (want > RAY_POOL_INIT_TASKS) want = RAY_POOL_INIT_TASKS;
tasks = want < n_segs ? want : n_segs;
if (tasks < 1) tasks = 1;
}
counts = ray_alloc_raw((size_t)tasks * sizeof(int64_t));
local = ray_alloc_raw((size_t)tasks * AGG_FIRSTN_LOCAL * sizeof(int64_t));
kvs = ray_alloc_raw((size_t)tasks * n_keys * sizeof(int64_t));
if (!counts || !local || !kvs) { res = ray_error("oom", NULL); goto done; }
agg_firstn_ctx_t c = { .set = &set, .key_cols = key_cols, .key_data = key_data,
.n_keys = n_keys, .nrows = nrows, .sel = sel, .n_segs = n_segs,
.tasks = tasks, .counts = counts, .local = local, .out = NULL, .kvs = kvs };
if (tasks > 1) ray_pool_dispatch_n(pool, agg_firstn_fn, &c, tasks);
else agg_firstn_fn(&c, 0, 0, 1);
if (agg_cancelled()) { res = ray_error("cancel", NULL); goto done; }
int64_t m = 0;
bool spilled = false;
for (uint32_t t = 0; t < tasks; t++) {
if (counts[t] > AGG_FIRSTN_LOCAL) spilled = true;
m += counts[t];
}
if (m * (AGG_FIRSTN_SHARE / 2) > eff_n) goto done;
idx = ray_alloc_raw((size_t)(m > 0 ? m : 1) * sizeof(int64_t));
if (!idx) { res = ray_error("oom", NULL); goto done; }
if (spilled) {
int64_t off = 0;
for (uint32_t t = 0; t < tasks; t++) { int64_t ct = counts[t]; counts[t] = off; off += ct; }
c.out = idx;
if (tasks > 1) ray_pool_dispatch_n(pool, agg_firstn_fn, &c, tasks);
else agg_firstn_fn(&c, 0, 0, 1);
if (agg_cancelled()) { res = ray_error("cancel", NULL); goto done; }
} else {
int64_t off = 0;
for (uint32_t t = 0; t < tasks; t++) {
memcpy(idx + off, local + (int64_t)t * AGG_FIRSTN_LOCAL, (size_t)counts[t] * sizeof(int64_t));
off += counts[t];
}
}
compact = agg_build_compact(g, op, tbl, idx, m);
if (!compact || RAY_IS_ERR(compact)) {
res = compact ? compact : ray_error("oom", NULL); compact = NULL; goto done;
}
firstn_nested = true;
sub = exec_group_v2_run(g, op, compact, m, NULL, NULL, 0, set.n, NULL);
firstn_nested = false;
if (!sub || RAY_IS_ERR(sub)) { res = sub ? sub : ray_error("oom", NULL); sub = NULL; goto done; }
if (sub->type != RAY_TABLE || ray_table_nrows(sub) != set.n ||
ray_table_ncols(sub) < (int64_t)n_keys)
goto done;
perm = ray_alloc_raw((size_t)set.n * sizeof(int64_t));
if (!perm) { res = ray_error("oom", NULL); goto done; }
for (int64_t r = 0; r < set.n; r++) perm[r] = -1;
bool identity = true;
for (int64_t r = 0; r < set.n; r++) {
for (uint32_t k = 0; k < n_keys; k++) {
ray_t* oc = ray_table_get_col_idx(sub, k);
if (!oc || oc->type != key_cols[k]->type) goto done;
kv[k] = agg_read_key_i64(oc, ray_data(oc), r);
}
int64_t rk = agg_firstn_probe(&set, kv, agg_firstn_hash(kv, n_keys), false);
if (rk < 0 || perm[rk] >= 0) goto done;
perm[rk] = r;
if (rk != r) identity = false;
}
if (identity) { res = sub; sub = NULL; }
else {
ray_t* pv = ray_vec_new(RAY_I64, set.n);
if (!pv || RAY_IS_ERR(pv)) { res = pv ? pv : ray_error("oom", NULL); goto done; }
memcpy(ray_data(pv), perm, (size_t)set.n * sizeof(int64_t));
pv->len = set.n;
res = ray_at_fn(sub, pv);
ray_release(pv);
if (!res) res = ray_error("oom", NULL);
}
if (!RAY_IS_ERR(res)) {
route_stats.first_n++;
ray_profile_tick("group: first-N keys");
}
done:
if (sub) ray_release(sub);
if (compact) ray_release(compact);
ray_free_raw(perm); ray_free_raw(idx); ray_free_raw(local); ray_free_raw(kvs); ray_free_raw(counts);
ray_free_raw(set.keys); ray_free_raw(set.rank); ray_free_raw(set.bloom);
if (kv_hdr) scratch_free(kv_hdr);
return res;
}
static ray_t* exec_group_v2_run_inner(ray_graph_t* g, ray_op_t* op, ray_t* tbl,
int64_t nrows, ray_t* sel,
const int64_t* sel_prefix, int64_t n_sel,
int64_t group_limit,
const ray_group_emit_filter_t* efp) {
agg_route_reason(AGG_V2_ADMITTED);
route_stats.topn_native = false;
route_stats.nullable_key = false;
route_stats.dense_plan_available = false;
route_stats.dense_worker_budget = false;
route_stats.dense_tasks = 0;
ray_op_ext_t* ext = find_ext(g, op->id);
ray_t* kc_hdr;
ray_t** key_cols = (ray_t**)scratch_alloc(&kc_hdr,
(size_t)ext->n_keys * (sizeof(ray_t*) + sizeof(int64_t)));
if (!key_cols) return ray_error("oom", NULL);
int64_t* key_syms = (int64_t*)(key_cols + ext->n_keys);
for (uint32_t k = 0; k < ext->n_keys; k++) {
ray_op_ext_t* kext = find_ext(g, ext->keys[k]);
key_cols[k] = ray_table_get_col(tbl, kext->sym);
key_syms[k] = kext->sym;
if (key_cols[k]->type != RAY_SYM && ray_vec_may_have_nulls(key_cols[k]))
route_stats.nullable_key = true;
}
bool indexed = false;
for (uint32_t k = 0; k < ext->n_keys; k++) {
int8_t t = key_cols[k]->type;
if (t == RAY_F32 || t == RAY_F64 || t == RAY_GUID || t == RAY_STR || t == RAY_LIST) indexed = true;
}
for (uint32_t a = 0; a < ext->n_aggs; a++) {
ray_op_ext_t* ie = find_ext(g, ext->agg_ins[a]);
ray_t* col = ie ? ray_table_get_col(tbl, ie->sym) : NULL;
if (col && agg_indexed_supported(ext->agg_ops[a], col->type)) indexed = true;
}
if (indexed) {
if (sel) {
ray_t* idx = ray_rowsel_to_indices(sel);
if (!idx || RAY_IS_ERR(idx)) { scratch_free(kc_hdr); return idx ? idx : ray_error("oom", NULL); }
ray_t* compact = agg_build_compact(g, op, tbl, ray_data(idx), n_sel);
ray_t* result = compact && !RAY_IS_ERR(compact)
? exec_group_v2_run(g, op, compact, n_sel, NULL, NULL, 0, group_limit, efp) : compact;
if (compact && !RAY_IS_ERR(compact)) ray_release(compact);
ray_release(idx); scratch_free(kc_hdr); return result;
}
agg_route_record(AGG_ROUTE_V2_INDEXED);
ray_t* result = agg_indexed_run(g, op, tbl, key_cols, key_syms, nrows);
scratch_free(kc_hdr); return result;
}
if (group_limit > 0 && !efp) {
ray_t* fr = agg_first_n_run(g, op, tbl, nrows, key_cols, sel, n_sel, group_limit);
if (fr) { scratch_free(kc_hdr); return fr; }
}
agg_vo_t vo;
if (!agg_vo_init(&vo, g, ext, tbl)) { scratch_free(kc_hdr); return ray_error("oom", NULL); }
const agg_vtable_t** vts = vo.vts; size_t* off = vo.off; size_t block = vo.block;
dense_plan_t dp;
bool dense = sel ? agg_dense_plan_sel(key_cols, ext->n_keys, n_sel, sel, sel_prefix, &dp)
: agg_dense_plan(key_cols, ext->n_keys, vts, ext->n_aggs, nrows, &dp);
route_stats.dense_plan_available = dense;
#define AGG_RUN_COMPACT_FALLBACK() \
do { \
agg_vo_free(&vo); \
agg_dense_plan_free(&dp); scratch_free(kc_hdr); \
ray_t* idxb = ray_rowsel_to_indices(sel); \
if (!idxb) return ray_error("oom", NULL); \
ray_t* compact = agg_build_compact(g, op, tbl, (int64_t*)ray_data(idxb), n_sel); \
if (!compact || RAY_IS_ERR(compact)) { \
ray_release(idxb); \
return compact ? compact : ray_error("oom", NULL); \
} \
ray_t* r = exec_group_v2_run(g, op, compact, n_sel, NULL, NULL, 0, \
group_limit, efp); \
ray_release(compact); ray_release(idxb); \
return r; \
} while (0)
int64_t eff_n = sel ? n_sel : nrows;
bool all_streaming = true;
for (uint32_t a = 0; a < ext->n_aggs && all_streaming; a++)
if (vts[a]->kind != ACC_STREAMING) all_streaming = false;
ray_pool_t* pool = ray_pool_get();
if (pool && eff_n >= RAY_PARALLEL_THRESHOLD && all_streaming) {
uint32_t dense_workers = ray_pool_total_workers(pool);
double scatter_budget = (double)eff_n * 2 * (8.0 * (ext->n_keys + ext->n_aggs + 1));
int64_t watermark = ray_heap_anon_watermark();
double dense_budget = scatter_budget;
if (dense_budget > (double)SIZE_MAX) dense_budget = (double)SIZE_MAX;
if (watermark > 0 && dense_budget > (double)watermark / 4) dense_budget = (double)watermark / 4;
double slab_bytes = dp.ok ? (double)dp.total_slots * (block + sizeof(int64_t) + 1) : 0;
uint64_t llc_bytes = ray_cache_llc_bytes();
double cache_budget = (llc_bytes ? (double)llc_bytes : 32.0 * 1048576.0) * 0.75;
uint32_t cache_tasks = dense_workers;
if (slab_bytes > 0 && slab_bytes * dense_workers > cache_budget) {
double fit = cache_budget / slab_bytes;
cache_tasks = fit >= 1 ? (uint32_t)fit : 0;
}
bool shared_plan = dp.ok && group_limit <= 0 && !sel && ext->n_keys == 1 && dp.total_slots >= 4096;
for (uint32_t a = 0; a < ext->n_aggs && shared_plan; a++)
shared_plan = vts[a]->update_shared != NULL;
if (shared_plan && slab_bytes <= dense_budget && agg_shared_sample(g, ext, tbl, key_cols, &vo, nrows)) {
route_stats.dense_worker_budget = false;
agg_route_record(AGG_ROUTE_V2_DENSE);
ray_t* result = exec_group_v2_parallel_dense(g, op, tbl, key_cols, key_syms, ext,
nrows, pool, &dp, dense_workers, AGG_DENSE_SHARED, sel, sel_prefix, n_sel, efp, 0);
agg_vo_free(&vo); agg_dense_plan_free(&dp); scratch_free(kc_hdr);
return result;
}
if (dp.ok && dense_workers > 1 && group_limit <= 0 && eff_n <= UINT32_MAX && dp.total_slots >= 4096) {
uint32_t sources = dense_workers;
if (sources > RAY_POOL_INIT_TASKS / 2) sources = RAY_POOL_INIT_TASKS / 2;
uint32_t parts = agg_dense_partition_parts(dense_workers, dp.total_slots);
uint32_t split_budget = sources * 4 < parts ? sources * 4 : parts;
int64_t part_slots = ((dp.total_slots + parts - 1) / parts + 7) & ~INT64_C(7);
agg_desc_t payload;
double record_size = (double)SIZE_MAX;
if (agg_desc_init(&payload, g, ext, tbl, key_cols)) {
record_size = agg_partition_record(&payload, ext->n_aggs, NULL, NULL);
agg_desc_free(&payload);
}
double bytes = 2.0 * part_slots * parts * (block + sizeof(int64_t))
+ (double)eff_n * (sizeof(uint32_t) + record_size + (sel ? sizeof(int64_t) : 0))
+ (double)(sources + 1) * parts * sizeof(uint64_t)
+ (double)(parts + split_budget) * sizeof(agg_dense_partition_task_t);
uint32_t local_tasks = cache_tasks < dense_workers ? cache_tasks : dense_workers;
double local_traffic = dense_workers * slab_bytes;
double partition_traffic = (double)eff_n * (sizeof(uint32_t) + record_size);
double partition_budget = scatter_budget + (double)eff_n * (block + sizeof(int64_t));
if (partition_budget > (double)SIZE_MAX) partition_budget = (double)SIZE_MAX;
if (watermark > 0 && partition_budget > (double)watermark / 4)
partition_budget = (double)watermark / 4;
bool cache_starved = local_tasks < 3 && local_tasks < dense_workers;
if (bytes <= partition_budget && (local_traffic > partition_traffic || cache_starved)) {
route_stats.dense_worker_budget = false;
agg_route_record(AGG_ROUTE_V2_DENSE);
ray_t* result = exec_group_v2_parallel_dense(g, op, tbl, key_cols, key_syms, ext,
nrows, pool, &dp, dense_workers, AGG_DENSE_PARTITIONED, sel, sel_prefix, n_sel, efp, 0);
agg_vo_free(&vo); agg_dense_plan_free(&dp); scratch_free(kc_hdr);
return result;
}
}
if (dp.ok && slab_bytes > 0) {
double fit = (dense_budget - (double)eff_n * sizeof(uint32_t)) / slab_bytes - 1;
if (fit < dense_workers) dense_workers = fit >= 2 ? (uint32_t)fit : 0;
if (cache_tasks < dense_workers) dense_workers = cache_tasks >= 2 ? cache_tasks : 0;
}
bool dense_par_ok = dp.ok && dense_workers > 0;
if (dense_par_ok && !sel && ext->n_keys == 1) {
const void* data = ray_data(key_cols[0]);
double touched_slots = 0;
for (uint32_t w = 0; w < dense_workers; w++) {
int64_t start = nrows / dense_workers * w;
int64_t end = w + 1 == dense_workers ? nrows : nrows / dense_workers * (w + 1);
int64_t samples = end - start < 1024 ? end - start : 1024;
int64_t lo = INT64_MAX, hi = INT64_MIN;
for (int64_t i = 0; i < samples; i++) {
int64_t r = start + i * ((end - start - 1) / (samples > 1 ? samples - 1 : 1));
int64_t v = agg_read_key_i64(key_cols[0], data, r);
if (dp.nullable[0] && v == dp.nulls[0]) continue;
if (v < lo) lo = v;
if (v > hi) hi = v;
}
if (lo <= hi) touched_slots += (double)((uint64_t)hi - (uint64_t)lo) + 1;
}
if (touched_slots * (block + sizeof(int64_t) + 1) > scatter_budget / 2)
dense_par_ok = false;
}
route_stats.dense_worker_budget = dp.ok && !dense_par_ok;
if (route_stats.nullable_key) ray_profile_tick("group: nullable key");
if (route_stats.dense_worker_budget) ray_profile_tick("group: dense worker budget exceeded");
bool keys_intsym = true;
for (uint32_t k = 0; k < ext->n_keys && keys_intsym; k++) {
ray_t* kc = key_cols[k];
switch (kc->type) {
case RAY_I64: case RAY_I32: case RAY_I16: case RAY_U8:
case RAY_BOOL: case RAY_DATE: case RAY_TIME:
case RAY_TIMESTAMP: case RAY_SYM: break;
default: keys_intsym = false;
}
}
if (dense_par_ok) {
uint32_t extra_tasks = dense_workers * 4;
if (extra_tasks > RAY_POOL_INIT_TASKS) extra_tasks = RAY_POOL_INIT_TASKS;
if (dense_workers > 1 && extra_tasks * slab_bytes <= scatter_budget / 8 &&
extra_tasks * slab_bytes <= cache_budget &&
(extra_tasks + 1.0) * slab_bytes + (double)eff_n * sizeof(uint32_t) <= dense_budget)
dense_workers = extra_tasks;
route_stats.dense_tasks = dense_workers;
route_stats.dense_local_slots = 0;
for (uint32_t w = 0; w < dense_workers; w++)
route_stats.dense_local_slots += dp.total_slots;
agg_route_record(AGG_ROUTE_V2_DENSE);
ray_t* r = exec_group_v2_parallel_dense(g, op, tbl, key_cols, key_syms, ext, nrows, pool, &dp, dense_workers, AGG_DENSE_TASK_LOCAL,
sel, sel_prefix, n_sel, efp, group_limit);
agg_vo_free(&vo); agg_dense_plan_free(&dp); scratch_free(kc_hdr); return r;
}
if (keys_intsym) {
ray_t* r = ray_agg_tablestack
? exec_group_v2_parallel_tablestack(g, op, tbl, nrows,
key_cols, key_syms, vts, off, block, sel, sel_prefix, n_sel,
group_limit, efp)
: NULL;
if (!r) {
agg_route_record(AGG_ROUTE_V2_RADIX);
r = exec_group_v2_parallel_radix(g, op, tbl, nrows,
key_cols, key_syms, vts, off, block, sel, sel_prefix, n_sel,
group_limit, efp);
}
agg_vo_free(&vo); agg_dense_plan_free(&dp); scratch_free(kc_hdr); return r;
}
if (sel) AGG_RUN_COMPACT_FALLBACK();
agg_route_record(AGG_ROUTE_V2_INDEXED);
{ ray_t* r = agg_indexed_run(g, op, tbl, key_cols, key_syms, nrows);
agg_vo_free(&vo); agg_dense_plan_free(&dp); scratch_free(kc_hdr); return r; }
}
agg_vo_free(&vo);
if (sel) AGG_RUN_COMPACT_FALLBACK();
agg_route_record(dense ? AGG_ROUTE_V2_SERIAL_DENSE : AGG_ROUTE_V2_SERIAL_HASH);
agg_groups_t groups = {0};
int grp_rc = dense ? agg_group_keys_dense(key_cols, nrows, &dp, &groups)
: agg_group_keys(key_cols, ext->n_keys, nrows, &groups);
if (grp_rc != 0) { agg_dense_plan_free(&dp); scratch_free(kc_hdr); return ray_error("oom", NULL); }
ray_t* result = ray_table_new(ext->n_keys + ext->n_aggs);
if (!result || RAY_IS_ERR(result)) { agg_dense_plan_free(&dp); scratch_free(kc_hdr); agg_groups_free(&groups); return ray_error("oom", NULL); }
for (uint32_t k = 0; k < ext->n_keys; k++) {
ray_t* kc = ray_group_gather(key_cols[k], groups.first_row, groups.ngroups);
if (!kc || RAY_IS_ERR(kc)) { agg_dense_plan_free(&dp); scratch_free(kc_hdr); agg_groups_free(&groups); ray_release(result); return kc ? kc : ray_error("oom", NULL); }
result = ray_table_add_col(result, key_syms[k], kc);
ray_release(kc);
}
agg_dense_plan_free(&dp); scratch_free(kc_hdr);
for (uint32_t a = 0; a < ext->n_aggs; a++) {
ray_op_ext_t* ie = find_ext(g, ext->agg_ins[a]);
int64_t kparam = (ext->agg_k ? ext->agg_k[a] : 0);
ray_t* col;
if (ext->agg_ins2 && ext->agg_ins2[a] != RAY_OP_NONE) {
ray_op_ext_t* ye = find_ext(g, ext->agg_ins2[a]);
ray_t* x_col = ray_table_get_col(tbl, ie->sym);
ray_t* y_col = ray_table_get_col(tbl, ye->sym);
const agg_vtable_t* vt = agg_resolve(ext->agg_ops[a], x_col->type);
col = agg_run_one_bin(vt, x_col, y_col, groups.gids, nrows, groups.ngroups, kparam);
} else {
ray_t* val_col = (ext->agg_ops[a] != OP_COUNT && ie) ? ray_table_get_col(tbl, ie->sym) : NULL;
int8_t in_type = val_col ? val_col->type : RAY_I64;
const agg_vtable_t* vt = agg_resolve(ext->agg_ops[a], in_type);
col = agg_run_one(vt, val_col, groups.gids, nrows, groups.ngroups, kparam);
}
if (!col || RAY_IS_ERR(col)) { agg_groups_free(&groups); ray_release(result); return col ? col : ray_error("oom", NULL); }
int64_t agg_name = agg_result_col_name(ie ? ie->sym : 0, ext->agg_ops[a]);
result = ray_table_add_col(result, agg_name, col);
ray_release(col);
}
agg_groups_free(&groups);
return result;
#undef AGG_RUN_COMPACT_FALLBACK
}
static ray_t* agg_build_compact(ray_graph_t* g, ray_op_t* op, ray_t* tbl,
int64_t* idx, int64_t n_sel) {
ray_op_ext_t* ext = find_ext(g, op->id);
size_t want_cap = (size_t)ext->n_keys + 2 * (size_t)ext->n_aggs;
ray_t* want_hdr = NULL;
int64_t* want = (int64_t*)scratch_alloc(&want_hdr, want_cap * sizeof(int64_t));
if (want_cap && !want) return ray_error("oom", NULL);
uint32_t n_want = 0;
for (uint32_t k = 0; k < ext->n_keys; k++) {
int64_t s = find_ext(g, ext->keys[k])->sym;
bool seen = false;
for (uint32_t i = 0; i < n_want; i++) if (want[i] == s) { seen = true; break; }
if (!seen) want[n_want++] = s;
}
for (uint32_t a = 0; a < ext->n_aggs; a++) {
if (ext->agg_ops[a] == OP_COUNT && !(ext->agg_ins && ext->agg_ins[a] != RAY_OP_NONE))
continue;
ray_op_t* ins[2] = { ext->agg_ins ? op_node(g, ext->agg_ins[a]) : NULL,
ext->agg_ins2 ? op_node(g, ext->agg_ins2[a]) : NULL };
for (int j = 0; j < 2; j++) {
if (!ins[j]) continue;
int64_t s = find_ext(g, ins[j]->id)->sym;
bool seen = false;
for (uint32_t i = 0; i < n_want; i++) if (want[i] == s) { seen = true; break; }
if (!seen) want[n_want++] = s;
}
}
ray_t* compact = ray_table_new(n_want);
if (!compact || RAY_IS_ERR(compact)) {
scratch_free(want_hdr);
return compact ? compact : ray_error("oom", NULL);
}
for (uint32_t i = 0; i < n_want; i++) {
ray_t* src = ray_table_get_col(tbl, want[i]);
if (!src) { ray_release(compact); scratch_free(want_hdr); return ray_error("nyi", NULL); }
ray_t* gcol = gather_by_idx(src, idx, n_sel);
if (!gcol || RAY_IS_ERR(gcol)) {
ray_release(compact); scratch_free(want_hdr);
return gcol ? gcol : ray_error("oom", NULL);
}
compact = ray_table_add_col(compact, want[i], gcol);
ray_release(gcol);
if (!compact || RAY_IS_ERR(compact)) { scratch_free(want_hdr); return compact ? compact : ray_error("oom", NULL); }
}
scratch_free(want_hdr);
return compact;
}
ray_t* exec_group_v2(ray_graph_t* g, ray_op_t* op, ray_t* tbl,
int64_t group_limit) {
if (agg_cancelled()) return ray_error("cancel", NULL);
ray_group_emit_filter_t ef = ray_group_emit_filter_active();
const ray_group_emit_filter_t* efp = ef.enabled ? &ef : NULL;
if (!g) return ray_error("nyi", NULL);
if (!g->selection)
return exec_group_v2_run(g, op, tbl, ray_table_nrows(tbl), NULL, NULL, 0,
group_limit, efp);
int64_t src_nrows = ray_table_nrows(tbl);
ray_rowsel_t* sm = ray_rowsel_meta(g->selection);
if (sm->nrows != src_nrows)
return exec_group_v2_run(g, op, tbl, src_nrows, NULL, NULL, 0, group_limit, efp);
int64_t n_sel = sm->total_pass;
ray_t* prefix_block = agg_sel_build_prefix(g->selection);
if (!prefix_block) return ray_error("oom", NULL);
const int64_t* sel_prefix = (const int64_t*)ray_data(prefix_block);
ray_t* saved_sel = g->selection;
g->selection = NULL;
ray_t* result = exec_group_v2_run(g, op, tbl, src_nrows, saved_sel, sel_prefix,
n_sel, group_limit, efp);
g->selection = saved_sel;
ray_release(prefix_block);
return result;
}
static inline int64_t agg_read_key_i64(ray_t* col, const void* data, int64_t row) {
switch (col->type) {
case RAY_I64: case RAY_TIMESTAMP: return ((const int64_t*)data)[row];
case RAY_I32: case RAY_DATE: case RAY_TIME: return ((const int32_t*)data)[row];
case RAY_I16: return ((const int16_t*)data)[row];
case RAY_U8: case RAY_BOOL: return ((const uint8_t*)data)[row];
case RAY_SYM: return (int64_t)ray_read_sym(data, row, col->type, col->attrs);
default: return 0;
}
}
ray_t* agg_run_one(const agg_vtable_t* vt, ray_t* val_col,
const uint32_t* gids, int64_t nrows, int64_t ngroups,
int64_t kparam) {
char* states = ray_calloc_raw((size_t)((size_t)(ngroups > 0 ? ngroups : 1)) * (vt->state_size));
if (!states) return ray_error("oom", NULL);
for (int64_t gi = 0; gi < ngroups; gi++)
vt->init(states + (size_t)gi * vt->state_size);
ray_valid_t valid = { val_col ? ray_data(val_col) : NULL,
val_col ? val_col->type : RAY_I64,
val_col ? ray_vec_may_have_nulls(val_col) : false };
const void* vals = val_col ? ray_data(val_col) : NULL;
vt->update_batch(states, vt->state_size, gids, vals, &valid, nrows, NULL);
bool is_list = (vt->out_type == RAY_LIST);
ray_t* out = is_list ? ray_list_new(ngroups) : ray_vec_new(vt->out_type, ngroups);
if (!out || RAY_IS_ERR(out)) {
if (vt->destroy)
for (int64_t gi = 0; gi < ngroups; gi++)
vt->destroy(states + (size_t)gi * vt->state_size);
ray_free_raw(states); return ray_error("oom", NULL);
}
out->len = ngroups;
for (int64_t gi = 0; gi < ngroups; gi++) {
const void* state = states + (size_t)gi * vt->state_size;
if (is_list) {
ray_t* cell = vt->finalize(state, NULL, kparam);
out = ray_list_set(out, gi, cell);
ray_release(cell);
} else if (agg_finalize_value(vt, state, out, gi, kparam)) {
out->attrs |= RAY_ATTR_HAS_NULLS;
}
if (vt->destroy) vt->destroy(states + (size_t)gi * vt->state_size);
}
ray_free_raw(states);
return out;
}
ray_t* agg_run_one_bin(const agg_vtable_t* vt, ray_t* x_col, ray_t* y_col,
const uint32_t* gids, int64_t nrows, int64_t ngroups,
int64_t kparam) {
char* states = ray_calloc_raw((size_t)((size_t)(ngroups > 0 ? ngroups : 1)) * (vt->state_size));
if (!states) return ray_error("oom", NULL);
for (int64_t gi = 0; gi < ngroups; gi++)
vt->init(states + (size_t)gi * vt->state_size);
ray_valid_t vx = { ray_data(x_col), x_col->type,
ray_vec_may_have_nulls(x_col) };
ray_valid_t vy = { ray_data(y_col), y_col->type,
ray_vec_may_have_nulls(y_col) };
vt->update_batch2(states, vt->state_size, gids,
ray_data(x_col), ray_data(y_col), &vx, &vy, nrows, NULL);
ray_t* out = ray_vec_new(vt->out_type, ngroups);
if (!out || RAY_IS_ERR(out)) {
if (vt->destroy)
for (int64_t gi = 0; gi < ngroups; gi++)
vt->destroy(states + (size_t)gi * vt->state_size);
ray_free_raw(states); return ray_error("oom", NULL);
}
out->len = ngroups;
for (int64_t gi = 0; gi < ngroups; gi++) {
if (agg_finalize_value(vt, states + (size_t)gi * vt->state_size, out, gi, kparam))
out->attrs |= RAY_ATTR_HAS_NULLS;
if (vt->destroy) vt->destroy(states + (size_t)gi * vt->state_size);
}
ray_free_raw(states);
return out;
}
static int agg_group_keys_dense(ray_t** key_cols, int64_t nrows,
const dense_plan_t* dp, agg_groups_t* out) {
if (ray_pool_par_dispatch_ok(ray_pool_get(), nrows, RAY_PARALLEL_THRESHOLD) &&
ray_pool_total_workers(ray_pool_get()) >= 4)
return agg_group_keys_parallel(key_cols, dp->n_keys, nrows, dp, out);
const void* data[16];
for (uint32_t k = 0; k < dp->n_keys; k++) data[k] = ray_data(key_cols[k]);
int32_t* slot2gid = ray_alloc_raw((size_t)dp->total_slots * sizeof(int32_t));
out->gids = ray_alloc_raw((size_t)(nrows > 0 ? nrows : 1) * sizeof(uint32_t));
out->first_row = ray_alloc_raw((size_t)(nrows > 0 ? nrows : 1) * sizeof(int64_t));
if (!slot2gid || !out->gids || !out->first_row) {
ray_free_raw(slot2gid); ray_free_raw(out->gids); ray_free_raw(out->first_row);
out->gids = NULL; out->first_row = NULL; return -1;
}
for (int64_t s = 0; s < dp->total_slots; s++) slot2gid[s] = -1;
int64_t ngroups = 0;
const bool compacted = dp->compacted;
for (int64_t r = 0; r < nrows; r++) {
int64_t slot = 0;
if (compacted) {
for (uint32_t k = 0; k < dp->n_keys; k++)
slot += agg_dense_component(dp, k, agg_read_key_i64(key_cols[k], data[k], r)) * dp->strides[k];
} else {
for (uint32_t k = 0; k < dp->n_keys; k++)
slot += agg_dense_component_raw(dp, k, agg_read_key_i64(key_cols[k], data[k], r)) * dp->strides[k];
}
int32_t gp = slot2gid[slot];
if (gp < 0) {
gp = (int32_t)ngroups;
slot2gid[slot] = gp;
out->first_row[ngroups] = r;
ngroups++;
}
out->gids[r] = (uint32_t)gp;
}
out->ngroups = ngroups;
ray_free_raw(slot2gid);
return 0;
}
static uint64_t agg_float_key(ray_t* col, const void* data, int64_t r) {
if (col->type == RAY_F32) {
float v = ((const float*)data)[r];
if (v != v && ray_vec_may_have_nulls(col)) return UINT64_C(0x7fc00000);
if (v == 0) return 0;
uint32_t bits; memcpy(&bits, &v, sizeof(bits)); return bits;
}
double v = ((const double*)data)[r];
if (v != v && ray_vec_may_have_nulls(col)) return UINT64_C(0x7ff8000000000000);
if (v == 0) return 0;
uint64_t bits; memcpy(&bits, &v, sizeof(bits)); return bits;
}
static bool agg_list_key_eq(ray_t* a, ray_t* b, const ray_group_sym_view_t* view) {
if (a == b) return true;
if (!a || !b) return false;
if (a->type == RAY_LIST && b->type == RAY_LIST) {
if (a->len != b->len) return false;
for (int64_t i = 0; i < a->len; i++)
if (!agg_list_key_eq(ray_list_get(a, i), ray_list_get(b, i), view)) return false;
return true;
}
if (a->type == b->type && (a->type == RAY_SYM || a->type == RAY_STR)) {
if (a->len != b->len) return false;
for (int64_t i = 0; i < a->len; i++) {
if (a->type == RAY_SYM) {
ray_t* x = ray_group_sym_read(view, ray_sym_vec_domain(a), ray_read_sym(ray_data(a), i, a->type, a->attrs));
ray_t* y = ray_group_sym_read(view, ray_sym_vec_domain(b), ray_read_sym(ray_data(b), i, b->type, b->attrs));
if (ray_str_cmp(x, y)) return false;
} else {
size_t nx = 0, ny = 0;
const char* x = ray_str_vec_get(a, i, &nx);
const char* y = ray_str_vec_get(b, i, &ny);
if (nx != ny || (nx && memcmp(x, y, nx))) return false;
}
}
return true;
}
return atom_eq(a, b);
}
static uint64_t agg_list_key_hash(ray_t* value, const ray_group_sym_view_t* view) {
if (!value || ray_is_atom(value)) return ray_atom_hash(value);
if (value->type == RAY_LIST) {
uint64_t hash = ray_hash_i64(value->len);
ray_t* const* children = ray_data(value);
for (int64_t i = 0; i < value->len; i++) hash = ray_hash_combine(hash, agg_list_key_hash(children[i], view));
return hash;
}
if (value->type == RAY_SYM || value->type == RAY_STR) {
uint64_t hash = ray_hash_i64(value->type);
for (int64_t i = 0; i < value->len; i++) {
size_t len = 0; const char* str;
if (value->type == RAY_SYM) {
ray_t* atom = ray_group_sym_read(view, ray_sym_vec_domain(value), ray_read_sym(ray_data(value), i, value->type, value->attrs));
str = ray_str_ptr(atom); len = ray_str_len(atom);
} else str = ray_str_vec_get(value, i, &len);
hash = ray_hash_combine(hash, ray_hash_bytes(str ? str : "", len));
}
return hash;
}
if (ray_is_vec(value)) {
size_t bytes = (size_t)value->len * ray_elem_size(value->type);
return ray_hash_combine(ray_hash_i64(value->type), ray_hash_bytes(ray_data(value), bytes));
}
return ray_atom_hash(value);
}
static inline uint64_t agg_key_hash_at(ray_t* col, const void* data, int64_t r, const ray_group_sym_view_t* view) {
if (col->type == RAY_LIST) return agg_list_key_hash(((ray_t* const*)data)[r], view);
if (col->type == RAY_F32 || col->type == RAY_F64) return ray_hash_i64((int64_t)agg_float_key(col, data, r));
if (col->type == RAY_GUID) return ray_hash_bytes((const char*)data + (size_t)r * 16, 16);
if (col->type == RAY_STR) {
size_t len = 0;
const char* s = ray_str_vec_get(col, r, &len);
return ray_hash_bytes(s ? s : "", s ? len : 0);
}
return (uint64_t)agg_read_key_i64(col, data, r);
}
static inline int agg_key_eq_at(ray_t* col, const void* data, int64_t a, int64_t b, const ray_group_sym_view_t* view) {
if (col->type == RAY_LIST) return agg_list_key_eq(((ray_t* const*)data)[a], ((ray_t* const*)data)[b], view);
if (col->type == RAY_F32 || col->type == RAY_F64) return agg_float_key(col, data, a) == agg_float_key(col, data, b);
if (col->type == RAY_GUID) return memcmp((const char*)data + (size_t)a * 16, (const char*)data + (size_t)b * 16, 16) == 0;
if (col->type == RAY_STR) {
size_t la = 0, lb = 0;
const char* sa = ray_str_vec_get(col, a, &la);
const char* sb = ray_str_vec_get(col, b, &lb);
return la == lb && (la == 0 || memcmp(sa, sb, la) == 0);
}
return agg_read_key_i64(col, data, a) == agg_read_key_i64(col, data, b);
}
typedef struct {
ray_t* source;
const void* data;
const int64_t* rows;
const int64_t* offsets;
const int64_t* counts;
int64_t* output;
_Atomic(bool) failed;
ray_group_sym_view_t symbols;
} agg_distinct_indexed_t;
static void agg_distinct_indexed_run(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_distinct_indexed_t* c = raw;
int64_t largest = 0;
for (int64_t g = start; g < end; g++) if (c->counts[g] > largest) largest = c->counts[g];
uint64_t capacity = 16;
while (capacity < (uint64_t)largest * 2) {
if (capacity > SIZE_MAX / sizeof(int64_t) / 2) { atomic_store(&c->failed, true); return; }
capacity *= 2;
}
int64_t* entries = ray_alloc_raw((size_t)capacity * sizeof(int64_t));
if (!entries) { atomic_store(&c->failed, true); return; }
for (int64_t g = start; g < end && !agg_cancelled(); g++) {
uint64_t size = 16;
while (size < (uint64_t)c->counts[g] * 2) size *= 2;
memset(entries, 0, (size_t)size * sizeof(int64_t));
int64_t distinct = 0;
for (int64_t i = 0; i < c->counts[g]; i++) {
int64_t row = c->rows[c->offsets[g] + i];
uint64_t slot = agg_key_hash_at(c->source, c->data, row, &c->symbols) & (size - 1);
while (entries[slot] && !agg_key_eq_at(c->source, c->data, row, entries[slot] - 1, &c->symbols))
slot = (slot + 1) & (size - 1);
if (!entries[slot]) { entries[slot] = row + 1; distinct++; }
}
c->output[g] = distinct;
}
ray_free_raw(entries);
}
ray_t* agg_count_distinct_indexed(ray_t* src, const int64_t* rows,
const int64_t* offsets, const int64_t* counts, int64_t groups) {
if (src->type != RAY_STR && src->type != RAY_GUID && src->type != RAY_LIST) return NULL;
ray_t* out = ray_vec_new(RAY_I64, groups);
if (!out || RAY_IS_ERR(out)) return out ? out : ray_error("oom", NULL);
out->len = groups;
agg_distinct_indexed_t c = {src, ray_data(src), rows, offsets, counts, ray_data(out), false, {0}};
if (src->type == RAY_LIST) ray_sym_strings_borrow(&c.symbols.strings, &c.symbols.count);
ray_group_dispatch(agg_distinct_indexed_run, &c, counts, groups);
if (agg_cancelled() || atomic_load(&c.failed)) {
ray_release(out); return ray_error(agg_cancelled() ? "cancel" : "oom", NULL);
}
return out;
}
typedef struct {
ray_t** keys;
const void** data;
uint32_t nkeys, tasks, init_tasks;
int64_t rows, capacity, init_pages;
const dense_plan_t* dense;
void* first;
bool narrow;
uint8_t* unique;
int64_t* offsets;
agg_groups_t* out;
ray_group_sym_view_t symbols;
} agg_key_build_t;
enum { AGG_DIRECTORY_INIT_BYTES = 2 * 1024 * 1024 };
static void agg_key_directory_init(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_key_build_t* c = raw;
#define KEY_DIRECTORY_INIT(TYPE, EMPTY) do { \
_Atomic TYPE* first_slots = c->first; \
uint64_t offset = (uintptr_t)c->first & (AGG_DIRECTORY_INIT_BYTES - 1); \
for (int64_t task = start; task < end; task++) { \
uint64_t lo = (uint64_t)(c->init_pages * task / c->init_tasks) * AGG_DIRECTORY_INIT_BYTES; \
uint64_t hi = (uint64_t)(c->init_pages * (task + 1) / c->init_tasks) * AGG_DIRECTORY_INIT_BYTES; \
int64_t begin = lo < offset ? 0 : (int64_t)((lo - offset) / sizeof(*first_slots)); \
int64_t limit = (int64_t)((hi - offset) / sizeof(*first_slots)); \
if (limit > c->capacity) limit = c->capacity; \
for (int64_t i = begin; i < limit; i++) atomic_init(&first_slots[i], EMPTY); \
} \
} while (0)
if (c->narrow) KEY_DIRECTORY_INIT(int32_t, INT32_MAX);
else KEY_DIRECTORY_INIT(int64_t, INT64_MAX);
#undef KEY_DIRECTORY_INIT
}
static void agg_key_directory_insert(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_key_build_t* c = raw;
#define KEY_DIRECTORY_INSERT(TYPE, EMPTY) do { \
_Atomic TYPE* first_slots = c->first; \
for (int64_t r = start; r < end; r++) { \
uint64_t slot = 0; \
if (c->dense) { \
for (uint32_t k = 0; k < c->nkeys; k++) \
slot += agg_dense_component(c->dense, k, agg_read_key_i64(c->keys[k], c->data[k], r)) \
* c->dense->strides[k]; \
} else { \
uint64_t hash = UINT64_C(1469598103934665603); \
for (uint32_t k = 0; k < c->nkeys; k++) { \
hash ^= agg_key_hash_at(c->keys[k], c->data[k], r, &c->symbols); \
hash *= UINT64_C(1099511628211); \
} \
slot = hash & (c->capacity - 1); \
} \
for (;;) { \
TYPE first = atomic_load_explicit(&first_slots[slot], memory_order_relaxed); \
if (!c->dense && first != EMPTY) { \
bool equal = true; \
for (uint32_t k = 0; k < c->nkeys && equal; k++) \
equal = agg_key_eq_at(c->keys[k], c->data[k], r, first, &c->symbols); \
if (!equal) { slot = (slot + 1) & (c->capacity - 1); continue; } \
} \
if (r < first && !atomic_compare_exchange_weak_explicit(&first_slots[slot], &first, (TYPE)r, \
memory_order_relaxed, memory_order_relaxed)) continue; \
c->out->gids[r] = (uint32_t)slot; \
break; \
} \
} \
} while (0)
if (c->narrow) KEY_DIRECTORY_INSERT(int32_t, INT32_MAX);
else KEY_DIRECTORY_INSERT(int64_t, INT64_MAX);
#undef KEY_DIRECTORY_INSERT
}
static void agg_key_directory_count(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_key_build_t* c = raw;
#define KEY_DIRECTORY_COUNT(TYPE, EMPTY) do { \
_Atomic TYPE* first_slots = c->first; \
for (int64_t task = start; task < end; task++) { \
int64_t begin = c->rows / c->tasks * task; \
int64_t limit = task + 1 == c->tasks ? c->rows : c->rows / c->tasks * (task + 1); \
int64_t count = 0; \
for (int64_t r = begin; r < limit; r++) { \
bool first = atomic_load_explicit(&first_slots[c->out->gids[r]], memory_order_relaxed) == r; \
c->unique[r] = first; \
count += first; \
} \
c->offsets[task + 1] = count; \
} \
} while (0)
if (c->narrow) KEY_DIRECTORY_COUNT(int32_t, INT32_MAX);
else KEY_DIRECTORY_COUNT(int64_t, INT64_MAX);
#undef KEY_DIRECTORY_COUNT
}
static void agg_key_directory_compact(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_key_build_t* c = raw;
#define KEY_DIRECTORY_COMPACT(TYPE, EMPTY) do { \
_Atomic TYPE* first_slots = c->first; \
for (int64_t task = start; task < end; task++) { \
int64_t begin = c->rows / c->tasks * task; \
int64_t limit = task + 1 == c->tasks ? c->rows : c->rows / c->tasks * (task + 1); \
int64_t gid = c->offsets[task]; \
for (int64_t r = begin; r < limit; r++) if (c->unique[r]) { \
c->out->first_row[gid] = r; \
atomic_store_explicit(&first_slots[c->out->gids[r]], (TYPE)gid++, memory_order_relaxed); \
} \
} \
} while (0)
if (c->narrow) KEY_DIRECTORY_COMPACT(int32_t, INT32_MAX);
else KEY_DIRECTORY_COMPACT(int64_t, INT64_MAX);
#undef KEY_DIRECTORY_COMPACT
}
static void agg_key_directory_remap(void* raw, uint32_t wid, int64_t start, int64_t end) {
(void)wid; agg_key_build_t* c = raw;
#define KEY_DIRECTORY_REMAP(TYPE, EMPTY) do { \
_Atomic TYPE* first_slots = c->first; \
for (int64_t r = start; r < end; r++) \
c->out->gids[r] = (uint32_t)atomic_load_explicit(&first_slots[c->out->gids[r]], memory_order_relaxed); \
} while (0)
if (c->narrow) KEY_DIRECTORY_REMAP(int32_t, INT32_MAX);
else KEY_DIRECTORY_REMAP(int64_t, INT64_MAX);
#undef KEY_DIRECTORY_REMAP
}
static int agg_group_keys_parallel(ray_t** keys, uint32_t nkeys, int64_t rows,
const dense_plan_t* dp, agg_groups_t* out) {
ray_pool_t* pool = ray_pool_get();
uint32_t tasks = ray_pool_total_workers(pool) * 4;
if (tasks > RAY_POOL_INIT_TASKS) tasks = RAY_POOL_INIT_TASKS;
int64_t cap = dp ? dp->total_slots : 16;
if (!dp) while (cap < rows * 2) cap *= 2;
agg_key_build_t c = {.keys = keys, .nkeys = nkeys, .tasks = tasks, .rows = rows,
.capacity = cap, .dense = dp, .out = out, .narrow = rows <= INT32_MAX};
for (uint32_t k = 0; k < nkeys; k++) if (keys[k]->type == RAY_LIST) {
ray_sym_strings_borrow(&c.symbols.strings, &c.symbols.count); break;
}
c.data = ray_alloc_raw((size_t)nkeys * sizeof(*c.data));
c.first = ray_alloc_raw((size_t)cap * (c.narrow ? sizeof(_Atomic(int32_t)) : sizeof(_Atomic(int64_t))));
c.unique = ray_alloc_raw((size_t)rows);
c.offsets = ray_calloc_raw((tasks + 1) * sizeof(*c.offsets));
out->gids = ray_alloc_raw((size_t)rows * sizeof(*out->gids));
out->first_row = NULL;
int rc = -1;
if (!c.data || !c.first || !c.unique || !c.offsets || !out->gids) goto done;
for (uint32_t k = 0; k < nkeys; k++) c.data[k] = ray_data(keys[k]);
ray_profile_tick("directory: allocated");
size_t entry_size = c.narrow ? sizeof(_Atomic(int32_t)) : sizeof(_Atomic(int64_t));
uint64_t init_bytes = ((uintptr_t)c.first & (AGG_DIRECTORY_INIT_BYTES - 1)) + (uint64_t)cap * entry_size;
c.init_pages = (init_bytes + AGG_DIRECTORY_INIT_BYTES - 1) / AGG_DIRECTORY_INIT_BYTES;
c.init_tasks = c.init_pages < tasks ? (uint32_t)c.init_pages : tasks;
if (c.init_tasks == 1) agg_key_directory_init(&c, 0, 0, 1);
else ray_pool_dispatch_n(pool, agg_key_directory_init, &c, c.init_tasks);
ray_profile_tick("directory: initialized");
ray_pool_dispatch(pool, agg_key_directory_insert, &c, rows);
ray_profile_tick("directory: inserted keys");
if (agg_cancelled()) goto done;
ray_pool_dispatch_n(pool, agg_key_directory_count, &c, tasks);
ray_profile_tick("directory: counted groups");
if (agg_cancelled()) goto done;
for (uint32_t t = 0; t < tasks; t++) c.offsets[t + 1] += c.offsets[t];
out->ngroups = c.offsets[tasks];
out->first_row = ray_alloc_raw((size_t)(out->ngroups ? out->ngroups : 1) * sizeof(*out->first_row));
if (!out->first_row) goto done;
ray_pool_dispatch_n(pool, agg_key_directory_compact, &c, tasks);
ray_profile_tick("directory: compacted groups");
ray_pool_dispatch(pool, agg_key_directory_remap, &c, rows);
ray_profile_tick("directory: remapped rows");
if (!agg_cancelled()) rc = 0;
done:
ray_free_raw(c.data); ray_free_raw(c.first); ray_free_raw(c.unique); ray_free_raw(c.offsets);
if (rc) agg_groups_free(out);
return rc;
}
int agg_group_keys(ray_t** key_cols, uint32_t n_keys, int64_t nrows, agg_groups_t* out) {
if (nrows < 0 || nrows > INT32_MAX) return -1;
if (ray_pool_par_dispatch_ok(ray_pool_get(), nrows, RAY_PARALLEL_THRESHOLD))
return agg_group_keys_parallel(key_cols, n_keys, nrows, NULL, out);
ray_t* data_hdr;
const void** data = (const void**)scratch_alloc(&data_hdr, (size_t)n_keys * sizeof(void*));
if (!data) return -1;
for (uint32_t k = 0; k < n_keys; k++) data[k] = ray_data(key_cols[k]);
ray_group_sym_view_t symbols = {0};
for (uint32_t k = 0; k < n_keys; k++) if (key_cols[k]->type == RAY_LIST) {
ray_sym_strings_borrow(&symbols.strings, &symbols.count); break;
}
int64_t cap = 16;
while (cap < nrows * 2) cap <<= 1;
uint64_t mask = (uint64_t)cap - 1;
int32_t* ht_gid = ray_alloc_raw((size_t)cap * sizeof(int32_t));
out->gids = ray_alloc_raw((size_t)(nrows > 0 ? nrows : 1) * sizeof(uint32_t));
out->first_row = ray_alloc_raw((size_t)(nrows > 0 ? nrows : 1) * sizeof(int64_t));
if (!ht_gid || !out->gids || !out->first_row) {
scratch_free(data_hdr);
ray_free_raw(ht_gid); ray_free_raw(out->gids); ray_free_raw(out->first_row);
out->gids = NULL; out->first_row = NULL; return -1;
}
for (int64_t i = 0; i < cap; i++) ht_gid[i] = -1;
int64_t ngroups = 0;
for (int64_t r = 0; r < nrows; r++) {
uint64_t h = 1469598103934665603ULL;
for (uint32_t k = 0; k < n_keys; k++) {
h ^= agg_key_hash_at(key_cols[k], data[k], r, &symbols); h *= 1099511628211ULL;
}
uint64_t slot = h & mask;
for (;;) {
int32_t gptr = ht_gid[slot];
if (gptr < 0) {
ht_gid[slot] = (int32_t)ngroups;
out->first_row[ngroups] = r;
out->gids[r] = (uint32_t)ngroups;
ngroups++;
break;
}
int64_t fr = out->first_row[gptr];
int eq = 1;
for (uint32_t k = 0; k < n_keys; k++) {
if (!agg_key_eq_at(key_cols[k], data[k], r, fr, &symbols)) { eq = 0; break; }
}
if (eq) { out->gids[r] = (uint32_t)gptr; break; }
slot = (slot + 1) & mask;
}
}
out->ngroups = ngroups;
ray_free_raw(ht_gid);
scratch_free(data_hdr);
return 0;
}
void agg_groups_free(agg_groups_t* out) {
if (!out) return;
ray_free_raw(out->gids); out->gids = NULL;
ray_free_raw(out->first_row); out->first_row = NULL;
}
static ray_t* agg_gather_col_at(ray_t* sc, const int64_t* first_row, int64_t n) {
if (sc->type == RAY_STR) {
if (n == 0) return ray_str_vec_from_parts(NULL, NULL, NULL, 0);
const char** ptrs = (const char**)ray_alloc_raw((size_t)n * sizeof(const char*));
uint32_t* lens = (uint32_t*)ray_alloc_raw((size_t)n * sizeof(uint32_t));
if (!ptrs || !lens) {
ray_free_raw(ptrs);
ray_free_raw(lens);
return ray_error("oom", NULL);
}
for (int64_t gi = 0; gi < n; gi++) {
size_t sl = 0;
const char* sp = ray_str_vec_get(sc, first_row[gi], &sl);
ptrs[gi] = sp ? sp : "";
lens[gi] = (uint32_t)(sp ? sl : 0);
}
ray_t* dst = ray_str_vec_from_parts(ptrs, lens, NULL, n);
ray_free_raw(ptrs);
ray_free_raw(lens);
return dst;
}
if (sc->type == RAY_LIST) {
ray_t* dst = ray_alloc((size_t)(n > 0 ? n : 1) * sizeof(ray_t*));
if (!dst || RAY_IS_ERR(dst)) return dst;
dst->type = RAY_LIST; dst->len = n;
ray_t** dout = (ray_t**)ray_data(dst);
ray_t** sitems = (ray_t**)ray_data(sc);
for (int64_t gi = 0; gi < n; gi++) { dout[gi] = sitems[first_row[gi]]; ray_retain(dout[gi]); }
return dst;
}
return ray_group_gather(sc, first_row, n);
}
ray_t* agg_select_distinct(ray_t* tbl, ray_t** key_cols, const int64_t* key_syms,
uint32_t nk, int64_t nrows,
const int64_t* keep_syms, int keep_n) {
agg_groups_t groups = {0};
if (agg_group_keys(key_cols, nk, nrows, &groups) != 0)
return ray_error("oom", NULL);
int64_t ncol = ray_table_ncols(tbl);
ray_t* result = ray_table_new(ncol);
if (!result || RAY_IS_ERR(result)) {
agg_groups_free(&groups);
return result ? result : ray_error("oom", NULL);
}
for (uint32_t k = 0; k < nk; k++) {
ray_t* kc = agg_gather_col_at(key_cols[k], groups.first_row, groups.ngroups);
if (!kc || RAY_IS_ERR(kc)) {
agg_groups_free(&groups); ray_release(result);
return kc ? kc : ray_error("oom", NULL);
}
result = ray_table_add_col(result, key_syms[k], kc);
ray_release(kc);
if (RAY_IS_ERR(result)) { agg_groups_free(&groups); return result; }
}
for (int64_t c = 0; c < ncol; c++) {
int64_t cn = ray_table_col_name(tbl, c);
bool is_key = false;
for (uint32_t k = 0; k < nk; k++) if (key_syms[k] == cn) { is_key = true; break; }
if (is_key) continue;
if (keep_syms) {
bool req = false;
for (int j = 0; j < keep_n; j++) if (keep_syms[j] == cn) { req = true; break; }
if (!req) continue;
}
ray_t* sc = ray_table_get_col_idx(tbl, c);
if (!sc) continue;
ray_t* dc = agg_gather_col_at(sc, groups.first_row, groups.ngroups);
if (!dc || RAY_IS_ERR(dc)) {
agg_groups_free(&groups); ray_release(result);
return dc ? dc : ray_error("oom", NULL);
}
result = ray_table_add_col(result, cn, dc);
ray_release(dc);
if (RAY_IS_ERR(result)) { agg_groups_free(&groups); return result; }
}
agg_groups_free(&groups);
return result;
}