Skip to content

Commit 0a22cc7

Browse files
committed
Assembler // Continue patching memory management issues.
1 parent 4523934 commit 0a22cc7

1 file changed

Lines changed: 45 additions & 76 deletions

File tree

Sources/Megrez/2_Assembler.swift

Lines changed: 45 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,17 @@ extension Megrez.Compositor {
2121
var visited = Set<SearchState>()
2222
var bestScore = [Int: Double]() // 追蹤每個位置的最佳分數
2323

24+
var stateCleaningTasks: [() -> ()] = []
25+
defer { stateCleaningTasks.forEach { $0() } }
26+
2427
// 初期化起始狀態。
2528
let leadingNode = Megrez.Node(keyArray: ["$LEADING"])
2629
let start = SearchState(
2730
node: leadingNode,
2831
position: 0,
2932
prev: nil,
30-
distance: 0
33+
distance: 0,
34+
cleaningTaskRegister: &stateCleaningTasks
3135
)
3236
openSet.enqueue(PrioritizedState(state: start))
3337
bestScore[0] = 0
@@ -67,7 +71,8 @@ extension Megrez.Compositor {
6771
node: nextNode,
6872
position: nextPos,
6973
prev: current,
70-
distance: newScore
74+
distance: newScore,
75+
cleaningTaskRegister: &stateCleaningTasks
7176
)
7277

7378
bestScore[nextPos] = newScore
@@ -76,18 +81,17 @@ extension Megrez.Compositor {
7681

7782
// 即時記憶體最佳化:當 visited 集合過大時進行部分清理
7883
if visited.count > 1_000 { // 可調整的閾值
79-
Self.partialCleanVisitedStates(visited: &visited, keepRecentCount: 500)
84+
// 簡單清理:保留最近的高分狀態,清理較舊的低分狀態
85+
let sortedStates = visited.sorted { $0.distance > $1.distance }
86+
let statesToRemove = Array(sortedStates.dropFirst(500))
87+
for state in statesToRemove {
88+
visited.remove(state)
89+
}
8090
}
8191
}
8292

8393
// 從最佳終止狀態重建路徑。
8494
guard let finalState = bestFinalState else {
85-
// 即使沒有找到最佳狀態,也需要清理所有建立的 SearchState 物件
86-
Self.batchCleanAllSearchStates(
87-
visited: visited,
88-
openSet: &openSet,
89-
leadingState: start
90-
)
9195
return []
9296
}
9397

@@ -103,67 +107,8 @@ extension Megrez.Compositor {
103107
// 備註:此處不需要手動 ASAN,因為沒有參據循環(Retain Cycle)。
104108
}
105109
assembledNodes = pathNodes.map(\.copy)
106-
107-
// 手動 ASAN:批次清理所有 SearchState 物件以防止記憶體洩漏
108-
// 包括 visited set 中的所有狀態、openSet 中剩餘的狀態,以及 leadingState
109-
Self.batchCleanAllSearchStates(
110-
visited: visited,
111-
openSet: &openSet,
112-
leadingState: start
113-
)
114110
return assembledNodes
115111
}
116-
117-
/// 部分清理已訪問狀態集合以控制記憶體使用
118-
/// - Parameters:
119-
/// - visited: 已訪問的狀態集合
120-
/// - keepRecentCount: 要保留的最近狀態數量
121-
private static func partialCleanVisitedStates(
122-
visited: inout Set<SearchState>,
123-
keepRecentCount: Int
124-
) {
125-
guard visited.count > keepRecentCount else { return }
126-
127-
// 按距離排序,保留分數較高的狀態
128-
let sortedStates = visited.sorted { $0.distance > $1.distance }
129-
let statesToRemove = Array(sortedStates.dropFirst(keepRecentCount))
130-
131-
// 先從 Set 中移除,再清理參據(避免 hash 不一致)
132-
for state in statesToRemove {
133-
visited.remove(state)
134-
state.node = nil
135-
state.prev = nil
136-
}
137-
}
138-
139-
/// 即時清理策略:直接清理各個資料結構,避免額外的 Set 集合
140-
/// - Parameters:
141-
/// - visited: 已訪問的狀態集合
142-
/// - openSet: 優先序列中剩餘的狀態
143-
/// - leadingState: 初始狀態
144-
private static func batchCleanAllSearchStates(
145-
visited: Set<SearchState>,
146-
openSet: inout HybridPriorityQueue<PrioritizedState>,
147-
leadingState: SearchState
148-
) {
149-
// 策略1: 直接清理 visited set 中的所有狀態
150-
for state in visited {
151-
state.node = nil
152-
state.prev = nil
153-
}
154-
155-
// 策略2: 直接清理 openSet 中剩餘的所有狀態
156-
while !openSet.isEmpty {
157-
if let prioritizedState = openSet.dequeue() {
158-
prioritizedState.state.node = nil
159-
prioritizedState.state.prev = nil
160-
}
161-
}
162-
163-
// 策略3: 清理 leadingState
164-
leadingState.node = nil
165-
leadingState.prev = nil
166-
}
167112
}
168113

169114
// MARK: - 搜尋狀態相關定義
@@ -179,11 +124,13 @@ extension Megrez.Compositor {
179124
/// - position: 在輸入串中的位置。
180125
/// - prev: 前一個狀態。
181126
/// - distance: 到達此狀態的累計分數。
127+
/// - cleaningTaskRegister: 登記自毀任務池。
182128
init(
183129
node: Megrez.Node?,
184130
position: Int,
185131
prev: SearchState?,
186-
distance: Double = Double(Int.min)
132+
distance: Double = Double(Int.min),
133+
cleaningTaskRegister: inout [() -> ()]
187134
) {
188135
self.node = node
189136
self.position = position
@@ -192,13 +139,16 @@ extension Megrez.Compositor {
192139
// 使用不可變的標識符來確保 hash 一致性
193140
self.originalNodeRef = node
194141
self.stableHash = Self.computeStableHash(node: node, position: position)
142+
cleaningTaskRegister.append { [weak self] in
143+
self?.cleanChainRecursively()
144+
}
195145
}
196146

197147
// MARK: Internal
198148

199-
var node: Megrez.Node? // 當前節點(可變,用於清理)
149+
weak var node: Megrez.Node? // 當前節點(可變,用於清理)
200150
let position: Int // 在輸入串中的位置
201-
var prev: SearchState? // 前一個狀態
151+
weak var prev: SearchState? // 前一個狀態
202152
var distance: Double // 累計分數
203153

204154
// MARK: - Hashable 協定實作
@@ -207,11 +157,30 @@ extension Megrez.Compositor {
207157
lhs.originalNodeRef === rhs.originalNodeRef && lhs.position == rhs.position
208158
}
209159

210-
/// 清理單一 SearchState 的參據
211-
/// 注意:由於新的清理策略是直接清理各個集合,這個方法現在主要用於向下相容
212-
func cleanState() {
213-
node = nil
214-
prev = nil
160+
/// 清理整個 SearchState 鏈條,從當前節點開始向後遞歸清理
161+
/// 採用深度優先遍歷策略,確保每個節點都被清理
162+
func cleanChainRecursively() {
163+
var visited = Set<ObjectIdentifier>()
164+
var stack: [SearchState] = []
165+
stack.append(self)
166+
167+
while !stack.isEmpty {
168+
let current = stack.removeLast()
169+
let currentId = ObjectIdentifier(current)
170+
171+
// 避免重複清理和無限循環
172+
guard !visited.contains(currentId) else { continue }
173+
visited.insert(currentId)
174+
175+
// 在清理前,將 prev 加入堆疊(如果存在)
176+
if let prevState = current.prev {
177+
stack.append(prevState)
178+
}
179+
180+
// 清理當前節點
181+
current.node = nil
182+
current.prev = nil
183+
}
215184
}
216185

217186
func hash(into hasher: inout Hasher) {

0 commit comments

Comments
 (0)