0004-NFA-state-memory-memory-management.patch
application/octet-stream
Filename: 0004-NFA-state-memory-memory-management.patch
Type: application/octet-stream
Part: 3
Message:
Re: Row pattern recognition
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch 0004
Subject: Row pattern recognition: Improve NFA state memory management
| File | + | − |
|---|---|---|
| src/backend/executor/nodeWindowAgg.c | 103 | 100 |
| src/include/nodes/execnodes.h | 0 | 1 |
From b7876ef1859647c93d783b8c2f70cdb0c43795d7 Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
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)