From b7876ef1859647c93d783b8c2f70cdb0c43795d7 Mon Sep 17 00:00:00 2001 From: Henson Choi Date: Thu, 15 Jan 2026 23:53:02 +0900 Subject: [PATCH 4/5] Row pattern recognition: Improve NFA state memory management --- src/backend/executor/nodeWindowAgg.c | 203 ++++++++++++++------------- src/include/nodes/execnodes.h | 1 - 2 files changed, 103 insertions(+), 101 deletions(-) diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index 4af9998aacf..f02bff4251a 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -261,8 +261,7 @@ static RPRNFAState *nfa_state_alloc(WindowAggState *winstate); static void nfa_state_free(WindowAggState *winstate, RPRNFAState *state); static void nfa_state_free_list(WindowAggState *winstate, RPRNFAState *list); static RPRNFAState *nfa_state_clone(WindowAggState *winstate, int16 elemIdx, - int16 altPriority, int16 *counts, - RPRNFAState *list); + int16 altPriority, int16 *counts); static bool nfa_evaluate_row(WindowObject winobj, int64 pos, bool *varMatched); static RPRNFAContext *nfa_context_alloc(WindowAggState *winstate); static void nfa_unlink_context(WindowAggState *winstate, RPRNFAContext *ctx); @@ -270,6 +269,7 @@ static void nfa_context_free(WindowAggState *winstate, RPRNFAContext *ctx); static RPRNFAContext *nfa_start_context(WindowAggState *winstate, int64 startPos); static void nfa_step(WindowAggState *winstate, RPRNFAContext *ctx, bool *varMatched, int64 pos); +static void nfa_finalize_all_contexts(WindowAggState *winstate, int64 lastPos); static void nfa_process_context(WindowAggState *winstate, RPRNFAContext *ctx, int64 currentPos, bool hasLimitedFrame, int64 frameOffset); static void nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, @@ -4791,11 +4791,7 @@ update_reduced_frame(WindowObject winobj, int64 pos) /* No more rows in partition? Finalize all contexts */ if (!rowExists) { - for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next) - { - if (ctx->states != NULL) - nfa_step(winstate, ctx, NULL, currentPos - 1); - } + nfa_finalize_all_contexts(winstate, currentPos - 1); /* Absorb completed contexts at partition boundary (SKIP PAST LAST ROW only) */ if (winstate->rpSkipTo == ST_PAST_LAST_ROW) nfa_absorb_contexts(winstate, NULL, currentPos - 1); @@ -4882,7 +4878,6 @@ static RPRNFAState * nfa_state_alloc(WindowAggState *winstate) { RPRNFAState *state; - int maxDepth = winstate->rpPattern->maxDepth; /* Try to reuse from free list first */ if (winstate->nfaStateFree != NULL) @@ -4893,18 +4888,11 @@ nfa_state_alloc(WindowAggState *winstate) else { /* Allocate in partition context for proper lifetime */ - MemoryContext oldContext = MemoryContextSwitchTo(winstate->partcontext); - state = palloc(winstate->nfaStateSize); - MemoryContextSwitchTo(oldContext); + state = MemoryContextAlloc(winstate->partcontext, winstate->nfaStateSize); } - /* initialize state - clear all depth counts */ - state->next = NULL; - state->elemIdx = 0; - state->altPriority = 0; - /* Initialize all depth counts to 0 using memset */ - if (maxDepth > 0) - memset(state->counts, 0, sizeof(int16) * maxDepth); + /* Initialize entire state to zero */ + memset(state, 0, winstate->nfaStateSize); return state; } @@ -4964,12 +4952,12 @@ nfa_states_equal(WindowAggState *winstate, RPRNFAState *s1, RPRNFAState *s2) * * Add a state to ctx->states at the END, only if no duplicate exists. * Returns true if state was added, false if duplicate found (state is freed). + * Earlier states have lower altPriority (lexical order), so existing wins. */ static bool nfa_add_state_unique(WindowAggState *winstate, RPRNFAContext *ctx, RPRNFAState *state) { RPRNFAState *s; - RPRNFAState *prev = NULL; RPRNFAState *tail = NULL; /* Check for duplicate and find tail */ @@ -4977,26 +4965,10 @@ nfa_add_state_unique(WindowAggState *winstate, RPRNFAContext *ctx, RPRNFAState * { if (nfa_states_equal(winstate, s, state)) { - /* - * Duplicate found - keep lower altPriority for lexical order. - * Lower altPriority means earlier alternative in pattern. - */ - if (state->altPriority < s->altPriority) - { - /* New state has better priority, replace existing */ - state->next = s->next; - if (prev == NULL) - ctx->states = state; - else - prev->next = state; - nfa_state_free(winstate, s); - return true; - } - /* Existing state has better/equal priority, discard new */ + /* Duplicate found - existing has better lexical order, discard new */ nfa_state_free(winstate, state); return false; } - prev = s; tail = s; } @@ -5014,12 +4986,11 @@ nfa_add_state_unique(WindowAggState *winstate, RPRNFAContext *ctx, RPRNFAState * * nfa_state_clone * * Clone a state with given elemIdx, altPriority and counts. - * Only copies counts up to elem->depth (not entire maxDepth). - * Prepends to the provided list and returns the new list head. + * Caller is responsible for linking the returned state. */ static RPRNFAState * nfa_state_clone(WindowAggState *winstate, int16 elemIdx, int16 altPriority, - int16 *counts, RPRNFAState *list) + int16 *counts) { RPRPattern *pattern = winstate->rpPattern; int maxDepth = pattern->maxDepth; @@ -5027,10 +4998,8 @@ nfa_state_clone(WindowAggState *winstate, int16 elemIdx, int16 altPriority, state->elemIdx = elemIdx; state->altPriority = altPriority; - /* nfa_state_alloc already zeroed all counts, now copy all depth levels */ if (counts != NULL && maxDepth > 0) memcpy(state->counts, counts, sizeof(int16) * maxDepth); - state->next = list; return state; } @@ -5039,55 +5008,39 @@ nfa_state_clone(WindowAggState *winstate, int16 elemIdx, int16 altPriority, * nfa_add_matched_state * * Record a matched state following SQL standard semantics. - * For greedy quantifiers, longer match wins. For alternation at the same - * match length, lexical order (lower altPriority) wins. + * Lexical order (lower altPriority) wins first. Among same lexical order, + * longer match wins (greedy). */ static void nfa_add_matched_state(WindowAggState *winstate, RPRNFAContext *ctx, RPRNFAState *state, int64 matchEndRow) { - bool shouldUpdate = false; + bool shouldUpdate = false; if (ctx->matchedState == NULL) - { - /* No previous match, always save */ shouldUpdate = true; - } else if (state->altPriority < ctx->matchedState->altPriority) - { - /* Better lexical order always wins (SQL standard preference) */ - shouldUpdate = true; - } + shouldUpdate = true; /* Better lexical order wins */ else if (state->altPriority == ctx->matchedState->altPriority && matchEndRow > ctx->matchEndRow) - { - /* Same lexical order, longer match wins (greedy) */ - shouldUpdate = true; - } + shouldUpdate = true; /* Same lexical order, longer wins */ if (shouldUpdate) { - /* Reuse existing matchedState or allocate from free list */ - if (ctx->matchedState == NULL) - ctx->matchedState = nfa_state_alloc(winstate); + /* Free old matchedState if exists */ + if (ctx->matchedState != NULL) + nfa_state_free(winstate, ctx->matchedState); - /* Copy state data */ - memcpy(ctx->matchedState, state, winstate->nfaStateSize); - ctx->matchedState->next = NULL; + /* Take ownership of the new state */ + ctx->matchedState = state; + state->next = NULL; ctx->matchEndRow = matchEndRow; } -} - -/* - * nfa_free_matched_state - * - * Return matchedState to free list for reuse. - */ -static void -nfa_free_matched_state(WindowAggState *winstate, RPRNFAState *state) -{ - if (state != NULL) + else + { + /* This state didn't win, free it */ nfa_state_free(winstate, state); + } } /* @@ -5176,9 +5129,7 @@ nfa_context_alloc(WindowAggState *winstate) else { /* Allocate in partition context for proper lifetime */ - MemoryContext oldContext = MemoryContextSwitchTo(winstate->partcontext); - ctx = palloc(sizeof(RPRNFAContext)); - MemoryContextSwitchTo(oldContext); + ctx = MemoryContextAlloc(winstate->partcontext, sizeof(RPRNFAContext)); } ctx->next = NULL; @@ -5229,7 +5180,7 @@ nfa_context_free(WindowAggState *winstate, RPRNFAContext *ctx) if (ctx->states != NULL) nfa_state_free_list(winstate, ctx->states); if (ctx->matchedState != NULL) - nfa_free_matched_state(winstate, ctx->matchedState); + nfa_state_free(winstate, ctx->matchedState); ctx->states = NULL; ctx->matchedState = NULL; @@ -5585,6 +5536,24 @@ nfa_step(WindowAggState *winstate, RPRNFAContext *ctx, bool *varMatched, int64 p } } +/* + * nfa_finalize_all_contexts + * + * Finalize all active contexts when partition ends. + * Calls nfa_step with NULL varMatched to complete without new row data. + */ +static void +nfa_finalize_all_contexts(WindowAggState *winstate, int64 lastPos) +{ + RPRNFAContext *ctx; + + for (ctx = winstate->nfaContext; ctx != NULL; ctx = ctx->next) + { + if (ctx->states != NULL) + nfa_step(winstate, ctx, NULL, lastPos); + } +} + /* * nfa_process_context * @@ -5628,7 +5597,8 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, { RPRPattern *pattern = winstate->rpPattern; RPRPatternElement *elements = pattern->elements; - RPRNFAState *pending = state; /* states to process in current row */ + RPRNFAState *pending = state; /* states to process in current row */ + RPRNFAState *pending_tail = state; /* tail for FIFO append */ while (pending != NULL) { @@ -5637,8 +5607,11 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, int16 count; int depth; + /* Pop from head */ state = pending; pending = pending->next; + if (pending == NULL) + pending_tail = NULL; state->next = NULL; Assert(state->elemIdx >= 0 && state->elemIdx < pattern->numElements); @@ -5684,7 +5657,7 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, { RPRNFAState *clone = nfa_state_clone(winstate, state->elemIdx, state->altPriority, - state->counts, NULL); + state->counts); nfa_add_state_unique(winstate, ctx, clone); } @@ -5701,7 +5674,6 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, { /* Match ends at current row since we matched */ nfa_add_matched_state(winstate, ctx, state, currentPos); - nfa_state_free(winstate, state); } else if (RPRElemIsEnd(nextElem)) { @@ -5710,8 +5682,12 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, * This ensures match end position is recorded at the row where * the last VAR matched, not the next row. */ - state->next = pending; - pending = state; + state->next = NULL; + if (pending_tail) + pending_tail->next = state; + else + pending = state; + pending_tail = state; } else { @@ -5743,7 +5719,6 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, { /* Match ends at previous row since current didn't match */ nfa_add_matched_state(winstate, ctx, state, currentPos - 1); - nfa_state_free(winstate, state); } else if (RPRElemIsVar(nextElem)) { @@ -5751,13 +5726,21 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, * Current row was NOT consumed (skip case), so next VAR * must be tried on the SAME row via pending list */ - state->next = pending; - pending = state; + state->next = NULL; + if (pending_tail) + pending_tail->next = state; + else + pending = state; + pending_tail = state; } else { - state->next = pending; - pending = state; + state->next = NULL; + if (pending_tail) + pending_tail->next = state; + else + pending = state; + pending_tail = state; } } else @@ -5770,7 +5753,6 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, { /* Already at FIN - match ends at current row */ nfa_add_matched_state(winstate, ctx, state, currentPos); - nfa_state_free(winstate, state); } else if (RPRElemIsAlt(elem)) { @@ -5781,25 +5763,34 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, * ALT doesn't consume a row - it's just a dispatch point. * All branches should be evaluated on the CURRENT row. * Set altPriority to branch's elemIdx for lexical order tracking. + * Append to pending_tail to maintain lexical order. */ while (altIdx >= 0 && altIdx < pattern->numElements) { RPRPatternElement *altElem = &elements[altIdx]; + RPRNFAState *newState; if (first) { state->elemIdx = altIdx; - state->altPriority = altIdx; /* lexical order */ - state->next = pending; - pending = state; + state->altPriority = altIdx; + newState = state; first = false; } else { - pending = nfa_state_clone(winstate, altIdx, altIdx, - state->counts, pending); + newState = nfa_state_clone(winstate, altIdx, altIdx, + state->counts); } + /* Append to tail for lexical order */ + newState->next = NULL; + if (pending_tail) + pending_tail->next = newState; + else + pending = newState; + pending_tail = newState; + altIdx = altElem->jump; } @@ -5827,8 +5818,12 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, /* Reached maximum - must exit, continue processing */ state->counts[depth] = 0; state->elemIdx = elem->next; - state->next = pending; - pending = state; + state->next = NULL; + if (pending_tail) + pending_tail->next = state; + else + pending = state; + pending_tail = state; } else { @@ -5856,14 +5851,18 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, exitState = nfa_state_clone(winstate, elem->next, exitAltPriority, - state->counts, NULL); + state->counts); exitState->counts[depth] = 0; if (RPRElemIsFin(nextElem)) { - /* Match ends at current row */ - exitState->next = pending; - pending = exitState; + /* Match ends at current row - append to pending */ + exitState->next = NULL; + if (pending_tail) + pending_tail->next = exitState; + else + pending = exitState; + pending_tail = exitState; } else { @@ -5881,8 +5880,12 @@ nfa_step_single(WindowAggState *winstate, RPRNFAContext *ctx, else { state->elemIdx = elem->next; - state->next = pending; - pending = state; + state->next = NULL; + if (pending_tail) + pending_tail->next = state; + else + pending = state; + pending_tail = state; } } } diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 89139583855..21ce39d0797 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -2616,7 +2616,6 @@ typedef struct WindowAggState * initials (list of String) */ RPRNFAContext *nfaContext; /* active matching contexts (head) */ RPRNFAContext *nfaContextTail; /* tail of active contexts (for reverse traversal) */ - RPRNFAContext *nfaContextPending; /* matched but awaiting earlier starts */ RPRNFAContext *nfaContextFree; /* recycled NFA context nodes */ RPRNFAState *nfaStateFree; /* recycled NFA state nodes */ Size nfaStateSize; /* pre-calculated RPRNFAState size */ -- 2.50.1 (Apple Git-155)