From a5ecc5f1ae83a6ad6d5522f826a1c6d2707264bc Mon Sep 17 00:00:00 2001 From: eunhhu Date: Wed, 30 Sep 2026 03:09:18 +0100 Subject: Count only the states the memory budget counts An event under a budget counted its states twice, before and after, walking every state. For the smallest event (a number added, no store) that cost 21 ns in an app of one state and 125 ns in one of 32 (+17 %, +38 %). The app now keeps a bit for each list (not a message's rows) and text when it opens its module, and walks those alone: +10 ns and +29 ns (+8 %, +9 %), and nothing without a budget. A served write costs tens of microseconds, so its throughput does not see it. --- core/module.c | 14 +++++++++----- core/module.h | 3 +++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/module.c b/core/module.c index cbf0efe..ac46fb3 100644 --- a/core/module.c +++ b/core/module.c @@ -2728,13 +2728,12 @@ static void clear_rows(PureApp *app) { void pure_app_memory(PureApp *app, size_t bytes) { app->memory = bytes; } size_t pure_app_state_bytes(const PureApp *app) { - const PureModule *m = app->module; size_t bytes = (app->outbox ? pure_list_size(app->outbox) : 0) + (app->heard ? pure_list_size(app->heard) : 0); - for (uint32_t i = 0; i < m->states; ++i) { - if (app->lists[i] && !(m->state_flags[i] & PURE_LIST_ROWS >> 8)) bytes += pure_list_size(app->lists[i]); - else if (app->texts[i]) bytes += pure_draft_size(app->texts[i]); + for (uint32_t left = app->counted; left; left &= left - 1) { + unsigned i = (unsigned)__builtin_ctz(left); + bytes += app->lists[i] ? pure_list_size(app->lists[i]) : app->texts[i] ? pure_draft_size(app->texts[i]) : 0; } - return bytes; /* a message's rows are its own, gone when it ends */ + return bytes; } /* One event: every handler for it runs in module order against pending @@ -3550,6 +3549,11 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module) if (old) remap_outputs(app, old, module, true); drop_passes(app); /* kept by instruction of the module they were made for */ app->module = module; + app->counted = 0; + for (uint32_t i = 0; i < module->states; ++i) + if (module->state_type[i] == PURE_STATE_TEXT || + (module->state_type[i] == PURE_STATE_LIST && !(module->state_flags[i] & PURE_LIST_ROWS >> 8))) + app->counted |= UINT32_C(1) << i; /* a message's rows are its own, gone when it ends */ ++app->changes; return PURE_OK; } diff --git a/core/module.h b/core/module.h index c11c682..69acd67 100644 --- a/core/module.h +++ b/core/module.h @@ -157,6 +157,8 @@ typedef struct { uint8_t form, relation; uint16_t field; uint32_t test; PureBloc enum { PURE_MODULE_RANGES = 64 }; typedef struct { uint8_t lower_relation, upper_relation; PureBlock lower, upper; } PureRange; +_Static_assert(PURE_MODULE_STATES <= 32, "a state is a bit of PureApp.counted"); + typedef struct { uint16_t states, inputs, views, handlers, fields, collections, constants, records, record_fields; uint32_t code_count; @@ -313,6 +315,7 @@ typedef struct { PureFault fault; uint32_t fault_pc; size_t memory; /* the budget of what the states hold (0: none): pure_app_memory */ + uint32_t counted; /* the states it counts, a bit each: lists (not a message's rows) and texts */ uint64_t ticks, events; /* The time events see as now, in milliseconds since 1970 (UTC). The host * sets it, so a script can replay any time; views never see it. */ -- cgit v1.2.3