diff options
| author | eunhhu <[email protected]> | 2026-10-06 05:08:16 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-06 05:08:16 +0100 |
| commit | b94de93a5ae36bab5e00b5b479afeb59e9702a61 (patch) | |
| tree | 48418e7d8477f3a055f0adfe2a6330dd623fd44c /core | |
| parent | 9cb6985c0d9459ccf4dbbab4b05109168c362f75 (diff) | |
| download | pure-b94de93a5ae36bab5e00b5b479afeb59e9702a61.tar.gz pure-b94de93a5ae36bab5e00b5b479afeb59e9702a61.zip | |
Batch: a batch's appends made at once (pure_list_add_many, a call a chunk of 128) when every list takes its items so (an event's list, no index into it, one append to it: FLAT not keyed at its end, DENSE by keys it holds at its count, HASHED by number; never past its arrays' limit) and the memory budget takes them all (each item only grows what it counts): columns grown and widened once, the first key there already the executor's EXISTS at its round; else round by round as before, tried at once again after a round that changed a list's form. 50,000 DENSE adds 5.18 to 0.65 ms (the compiled code 0.28). APPEND_SOURCE: keys there already at rounds q and p, keys past a DENSE list's from round p, two appends to one list, widened columns, under memory budgets (seeds 1, 2, 3, 5, 23 alike: 31,402 batches at once in seed 23's, 292 by the budget round by round)
Diffstat (limited to 'core')
| -rw-r--r-- | core/module-batch.c | 170 | ||||
| -rw-r--r-- | core/module-list.c | 123 | ||||
| -rw-r--r-- | core/module-list.h | 12 |
3 files changed, 291 insertions, 14 deletions
diff --git a/core/module-batch.c b/core/module-batch.c index 6fe4a19..9905d43 100644 --- a/core/module-batch.c +++ b/core/module-batch.c @@ -1536,6 +1536,159 @@ static bool settle(Batch *b) { return true; } +enum { CHUNK = 128 }; /* items a call of pure_list_add_many takes */ + +/* v in the k rounds from r on, in their order (each round's lane's). */ +static void rounds_of(const Batch *b, const Val *v, unsigned r, unsigned k, double *out) { + if (v->kind == UNIFORM) + for (unsigned i = 0; i < k; ++i) out[i] = v->value; + else if (!b->reverse) { + if (v->kind == AFFINE) + for (unsigned i = 0; i < k; ++i) out[i] = v->value + v->step * (r + i); + else if (v->u32) + for (unsigned i = 0; i < k; ++i) out[i] = ((const uint32_t *)v->lane)[r + i]; + else + memcpy(out, (const double *)v->lane + r, k * sizeof(double)); + } else { + unsigned top = b->n - 1 - r; /* round r's lane, the lanes going down */ + if (v->kind == AFFINE) + for (unsigned i = 0; i < k; ++i) out[i] = v->value + v->step * (top - i); + else if (v->u32) + for (unsigned i = 0; i < k; ++i) out[i] = ((const uint32_t *)v->lane)[top - i]; + else + for (unsigned i = 0; i < k; ++i) out[i] = ((const double *)v->lane)[top - i]; + } +} + +/* The rounds from *r on that mask runs (NULL: every one), at most CHUNK of + * them, v's in each into out (rounds: which, when mask is not NULL); *r + * past the last. How many. */ +static unsigned chunk_of(const Batch *b, const Val *v, const uint32_t *mask, unsigned *r, unsigned n, double *out, unsigned *rounds) { + if (!mask) { + unsigned k = n - *r < CHUNK ? n - *r : CHUNK; + rounds_of(b, v, *r, k, out); + *r += k; + return k; + } + unsigned k = 0; + for (; *r < n && k < CHUNK; ++*r) { + unsigned l = round_of(b, *r); + if (mask[l]) rounds[k] = *r, out[k++] = lane_of(v, l); + } + return k; +} + +/* The batch's appends from round from on at once, each list's items by + * one call a chunk (pure_list_add_many), when every list takes its items + * so and the memory budget takes them all (each item only grows what the + * budget counts, so no round in between runs out): as the executor's steps + * make them round by round, the first key there already (an append's at + * its round, the earliest) its fault. false, nothing changed: make them + * round by round. */ +static bool at_once(Batch *b, Frame *frame, unsigned from, unsigned n, uint64_t per, int *how) { + PureApp *app = b->app; + const PureModule *m = b->m; + double keys[BATCH]; + size_t stored = 0, extra = 0; + for (unsigned i = 0; i < b->append_count; ++i) { + const Append *a = &b->appends[i]; + uint32_t arg = m->code[a->pc].arg; + int key = m->state_key[arg] ? (int)m->state_key[arg] - 1 : -1; + if (!(m->state_flags[arg] & PURE_LIST_ROWS >> 8) || m->state_indexed[arg]) return false; /* an event's list, no index into it */ + for (unsigned j = 0; j < i; ++j) + if (m->code[b->appends[j].pc].arg == arg) return false; /* two appends to one list: their items in turn */ + const uint32_t *mask = mask_lanes(b, a->mask); + size_t count = 0, s, e; + if (!mask) { + count = n - from; + if (key >= 0) rounds_of(b, &a->cells[key], from, n - from, keys); + } else + for (unsigned r = from; r < n; ++r) { + unsigned l = round_of(b, r); + if (!mask[l]) continue; + if (key >= 0) keys[count] = lane_of(&a->cells[key], l); + ++count; + } + if (!pure_list_takes(app->lists[arg], key, count, keys, &s, &e)) return false; + stored += s, extra += e; + } + if ((app->memory && app->grown + (int64_t)stored > (int64_t)app->memory) || app->spent + (int64_t)(stored + extra) > (int64_t)app->room) + return false; /* it runs out at some round: there, round by round */ + unsigned fault = n, faulting = 0; + for (unsigned i = 0; i < b->append_count; ++i) { + const Append *a = &b->appends[i]; + PureList *list = app->lists[m->code[a->pc].arg]; + const uint32_t *mask = mask_lanes(b, a->mask); + size_t was = pure_list_size(list), had = pure_list_extra(list); + double cells[PURE_RECORD_FIELDS][CHUNK]; + const double *fields[PURE_RECORD_FIELDS]; + unsigned rounds[CHUNK]; + for (unsigned f = 0; f < a->count; ++f) fields[f] = cells[f]; + for (unsigned r = from; r < n && r < fault;) { + unsigned at = r, count = 0; + for (unsigned f = 0; f < a->count; ++f) { + at = r; + count = chunk_of(b, &a->cells[f], mask, &at, n, cells[f], rounds); + } + if (!mask) + for (unsigned k = 0; k < count; ++k) rounds[k] = r + k; + r = at; + size_t added = count ? pure_list_add_many(list, count, fields) : 0; + if (added == SIZE_MAX) { /* no memory: the event stops there, as the executor's step would */ + app->work += (uint64_t)rounds[0] * per; + frame->status = PURE_ALLOCATION, frame->fault_pc = a->pc, frame->sp = 0; + *how = STEP_REFUSED; + return true; + } + if (added < count) { /* its key there already: the executor's fault at that round, unless one comes before it */ + if (rounds[added] < fault) fault = rounds[added], faulting = i; + break; + } + } + app->grown += (int64_t)pure_list_size(list) - (int64_t)was; /* as grows counts them, item by item */ + app->spent += (int64_t)(pure_list_size(list) + pure_list_extra(list)) - (int64_t)(was + had); + } + *how = STEP_NEXT; + if (fault < n) { /* the event's undone: only the fault and the work so far tell */ + app->work += (uint64_t)fault * per; + frame->kind = PURE_FAULT_EXISTS, frame->fault_pc = b->appends[faulting].pc, frame->sp = 0; + *how = STEP_FAULT; + } + return true; +} + +/* The batch's appends round by round, as the executor's steps make them: + * at once where they may (at_once), tried again after a round that changed + * a list's form (a keyed list's first items make it DENSE or HASHED). */ +static int append_items(Batch *b, Frame *frame, unsigned n, uint64_t per) { + PureApp *app = b->app; + unsigned forms[APPENDS]; + bool again = true; + for (unsigned r = 0; r < n; ++r) { + if (again) { + int how; + if (at_once(b, frame, r, n, per, &how)) return how; + for (unsigned i = 0; i < b->append_count; ++i) forms[i] = pure_list_form(app->lists[b->m->code[b->appends[i].pc].arg]); + again = false; + } + unsigned l = round_of(b, r); + for (unsigned i = 0; i < b->append_count; ++i) { + const Append *a = &b->appends[i]; + const uint32_t *mask = mask_lanes(b, a->mask); + if (mask && !mask[l]) continue; + for (unsigned f = 0; f < a->count; ++f) frame->stack[f].num = lane_of(&a->cells[f], l); + frame->sp = a->count; + int how = pure_run_step(frame, a->pc); + if (how != STEP_NEXT) { /* its fault at its round: the event's undone, so only how and the work so far tell */ + app->work += (uint64_t)r * per; + return how; + } + again |= pure_list_form(app->lists[b->m->code[a->pc].arg]) != forms[i]; + } + } + return STEP_NEXT; +} + int pure_batch_run(Frame *frame, Loop *loop, unsigned *done) { PureApp *app = frame->app; const PureModule *m = frame->m; @@ -1643,20 +1796,9 @@ int pure_batch_run(Frame *frame, Loop *loop, unsigned *done) { } for (unsigned i = 0; i < b->scatter_count; ++i) pure_list_touched(b->app->lists[b->scatters[i].list]); } - for (unsigned r = 0; r < n && b->append_count; ++r) { /* the appends round by round, each as the executor's step makes it */ - unsigned l = round_of(b, r); - for (unsigned i = 0; i < b->append_count; ++i) { - const Append *a = &b->appends[i]; - const uint32_t *mask = mask_lanes(b, a->mask); - if (mask && !mask[l]) continue; - for (unsigned f = 0; f < a->count; ++f) frame->stack[f].num = lane_of(&a->cells[f], l); - frame->sp = a->count; - int how = pure_run_step(frame, a->pc); - if (how != STEP_NEXT) { /* its fault at its round: the event's undone, so only how and the work so far tell */ - app->work += (uint64_t)r * per; - return how; - } - } + if (b->append_count) { + int how = append_items(b, frame, n, per); + if (how != STEP_NEXT) return how; } app->work += work; for (unsigned r = 0; r < b->reduce_count; ++r) frame->ev->locals[b->reduce[r].local].num = b->reduce[r].result; diff --git a/core/module-list.c b/core/module-list.c index 6da4a43..eb72c44 100644 --- a/core/module-list.c +++ b/core/module-list.c @@ -1816,6 +1816,129 @@ bool pure_list_widen(PureList *list, unsigned field, uint8_t bytes) { return wid void pure_list_touched(PureList *list) { ++list->version; } +bool pure_list_takes(const PureList *list, int key, size_t n, const double *keys, size_t *stored, size_t *extra) { + const Layout *l = &list->layout; + if (!list->arrays || list->pack || list->copy || list->journaled || l->key != key || n > PURE_LIST_ITEMS - list->count) return false; + for (unsigned f = 0; f < l->fields; ++f) + if (l->kinds[f] != PURE_CELL_NUMBER && l->kinds[f] != PURE_CELL_BOOL) return false; + size_t row = row_width(l), capacity = list->member_capacity; + if (key < 0) { /* at its end, FLAT: its arrays grown as flat_insert grows them, never past ARRAYS_LARGE (a tree then) */ + if (list->form != FLAT) return false; + for (size_t c = list->count, room = list->room; c < list->count + n; ++c) + if (c == room) { + if (2 * room * row > ARRAYS_LARGE) return false; + room = room ? 2 * room : 16; + } + } else if (list->form == DENSE && list->bitmap) { /* every key one it holds at its count now (it holds more as it grows) */ + size_t limit = dense_limit(list), top = 0; + for (size_t i = 0; i < n; ++i) { + if (!small_key(keys[i]) || (keys[i] == 0 && signbit(keys[i])) || (size_t)keys[i] >= limit) return false; + if ((size_t)keys[i] > top) top = (size_t)keys[i]; + } + if (n && top / 64 >= capacity) /* its bits grown as dense_add grows them */ + for (capacity = capacity ? capacity : 1; capacity <= top / 64;) capacity *= 2; + } else if (list->form == HASHED && list->text_key < 0) { /* its table doubled as hashed_add doubles it, never past ARRAYS_LARGE (made ordered then) */ + for (size_t c = list->count; c < list->count + n; ++c) + if (hashed_full(c + 1, capacity)) { + if (2 * capacity * row > ARRAYS_LARGE) return false; + capacity *= 2; + } + } else + return false; + *stored = n * l->number_bytes; + *extra = (capacity - list->member_capacity) * sizeof(*list->members); + return true; +} + +/* n numbers into a column of cells bytes wide from row at on. */ +static void column_out(void *column, uint8_t bytes, size_t at, const double *values, size_t n) { + switch (bytes) { + case 1: for (size_t i = 0; i < n; ++i) ((uint8_t *)column)[at + i] = (uint8_t)values[i]; break; + case 4: for (size_t i = 0; i < n; ++i) ((uint32_t *)column)[at + i] = (uint32_t)values[i]; break; + case 8: memcpy((double *)column + at, values, n * sizeof(double)); break; + } +} + +size_t pure_list_add_many(PureList *list, size_t n, const double *const *fields) { + const Layout *l = &list->layout; + for (unsigned f = 0; f < l->fields; ++f) { /* every column wide enough for them all first: widths only grow, as arrays_fit grows them */ + uint8_t now = list->width[f], need = now; + if ((now != 1 && now != 4) || !narrows(l->kinds[f])) continue; + for (size_t i = 0; i < n && need < 8; ++i) { + uint8_t w = width_of(fields[f][i]); + if (w > need) need = w; + } + if (need > now && !widen(list, f, need)) return SIZE_MAX; + } + size_t done = 0; + if (l->key < 0) { /* at its end */ + size_t room = list->room; + while (room < list->count + n) room = room ? 2 * room : 16; + if (room != list->room && !arrays_room(list, room)) return SIZE_MAX; + for (unsigned f = 0; f < l->fields; ++f) column_out(list->columns[f], list->width[f], list->count, fields[f], n); + done = n; + list->count += n; + } else if (list->form == DENSE) { /* at their keys, each set in its bits in turn: one there already ends them */ + const double *keys = fields[l->key]; + size_t room = list->room, top = 0, row = row_width(l); + for (size_t i = 0; i < n; ++i) { /* the room dense_add would leave, key after key */ + size_t key = (size_t)keys[i], limit = 4 * (list->count + i + 1) + DENSE_SLACK / row; + if (limit > ARRAYS_LARGE / row) limit = ARRAYS_LARGE / row; + if (key >= room) { + size_t grown = room + room / 2; + if (grown <= key) grown = key + 1 + (key + 1) / 2; + room = grown > limit ? limit : grown; + } + if (key > top) top = key; + } + if (room != list->room && !arrays_room(list, room)) return SIZE_MAX; + if (n && top / 64 >= list->member_capacity) { + size_t capacity = list->member_capacity ? list->member_capacity : 1; + while (capacity <= top / 64) capacity *= 2; + uint64_t *grown = realloc(list->members, capacity * sizeof(*grown)); + if (!grown) return SIZE_MAX; + memset(grown + list->member_capacity, 0, (capacity - list->member_capacity) * sizeof(*grown)); + list->members = grown, list->member_capacity = capacity; + } + for (; done < n; ++done) { + size_t key = (size_t)keys[done]; + uint64_t bit = UINT64_C(1) << (key % 64); + if (list->members[key / 64] & bit) break; + list->members[key / 64] |= bit; + } + for (unsigned f = 0; f < l->fields; ++f) { + void *column = list->columns[f]; + const double *values = fields[f]; + switch (list->width[f]) { + case 1: for (size_t i = 0; i < done; ++i) ((uint8_t *)column)[(size_t)keys[i]] = (uint8_t)values[i]; break; + case 4: for (size_t i = 0; i < done; ++i) ((uint32_t *)column)[(size_t)keys[i]] = (uint32_t)values[i]; break; + case 8: for (size_t i = 0; i < done; ++i) ((double *)column)[(size_t)keys[i]] = values[i]; break; + } + } + list->member_count += done; + list->count += done; + } else { /* HASHED, at their keys' slots: one there already ends them */ + for (; done < n; ++done) { + if (hashed_full(list->count + 1, list->member_capacity) && !hashed_table(list, 2 * list->member_capacity)) { + list->version += done; + list->bytes += done * l->number_bytes; + return SIZE_MAX; + } + uint64_t bits = key_bits(fields[l->key][done]); + size_t mask = list->member_capacity - 1, at = member_slot(bits, mask); + while (list->members[at] != VACANT && list->members[at] != bits) at = (at + 1) & mask; + if (list->members[at] == bits) break; + list->members[at] = bits; + for (unsigned f = 0; f < l->fields; ++f) cell_out(list->columns[f], list->width[f], at, (PureCell){.num = fields[f][done]}); + ++list->member_count; + ++list->count; + } + } + list->version += done; + list->bytes += done * l->number_bytes; + return done; +} + bool pure_list_settle(PureList *list) { if (list->pack || list->copy || list->arrays || list->form != TREE || list->layout.key < 0 || list->count < PACK_ITEMS || list->searched_version != list->version || list->searched < list->count / PACK_SEARCHES) diff --git a/core/module-list.h b/core/module-list.h index 28c1936..c44366d 100644 --- a/core/module-list.h +++ b/core/module-list.h @@ -186,6 +186,18 @@ bool pure_list_cells(PureList *, PureCells *); bool pure_list_widen(PureList *, unsigned field, uint8_t bytes); /* A batch set cells of it in place: it changed (its version). */ void pure_list_touched(PureList *); +/* Whether n items of numbers and bools go into an event's list at once + * (pure_list_add_many) as one after another they would go, the list + * staying in its form: a FLAT list not keyed (key -1), at its end; a DENSE + * one by keys it holds at its count now; a HASHED one by number (keys: the + * items' key field's cells, in order). *stored and *extra: what + * pure_list_size and pure_list_extra grow by once they are in. */ +bool pure_list_takes(const PureList *, int key, size_t n, const double *keys, size_t *stored, size_t *extra); +/* The n items (fields[f][i]: item i's field f) into a list that takes + * them, as pure_list_insert at its end or pure_list_add puts each in turn + * (its columns grown and widened once): how many went in before one whose + * key it had already (n: all of them); SIZE_MAX when memory ran out. */ +size_t pure_list_add_many(PureList *, size_t n, const double *const *fields); /* What it keeps its items in now: 0 a tree, 1 FLAT arrays, 2 DENSE (for tests and counts). */ unsigned pure_list_form(const PureList *); /* Field of the item whose key_field is key, in a list ordered by key_field |
