@@ -73,19 +73,38 @@ private static class Node {
7373 private Node firstChild ;
7474 private Node lastChild ;
7575
76- // Vars used during saving:
77-
78- // The file pointer point to where the node saved. -1 means the node has not been saved.
79- private long fp = -1 ;
80- // The latest child that have been saved. null means no child has been saved.
81- private Node savedTo ;
82-
8376 Node (int label , Output output ) {
8477 this .label = label ;
8578 this .output = output ;
8679 }
8780 }
8881
82+ /**
83+ * Transient state used only during {@link #saveNodes}. Keeps save-time bookkeeping off of {@link
84+ * Node} so that Nodes are smaller during the (much longer) building phase.
85+ */
86+ private static class SaveFrame {
87+ final Node node ;
88+ // Iteration cursor: the latest child that has been pushed for saving.
89+ Node savedTo ;
90+ // File pointer assigned when this node is written. -1 until then.
91+ long fp = -1 ;
92+ // File pointers of children, collected as they are popped. Null for leaf nodes.
93+ long [] childFps ;
94+ int numChildFps ;
95+
96+ SaveFrame (Node node ) {
97+ this .node = node ;
98+ if (node .childrenNum > 0 ) {
99+ this .childFps = new long [node .childrenNum ];
100+ }
101+ }
102+
103+ void addChildFp (long childFp ) {
104+ childFps [numChildFps ++] = childFp ;
105+ }
106+ }
107+
89108 private final Node root = new Node (0 , null );
90109 private final BytesRef minKey ;
91110 private BytesRef maxKey ;
@@ -202,31 +221,35 @@ void save(DataOutput meta, IndexOutput index) throws IOException {
202221 throw new IllegalStateException ("only unsaved trie can be saved, got: " + status );
203222 }
204223 meta .writeVLong (index .getFilePointer ());
205- saveNodes (index );
206- meta .writeVLong (root .fp );
224+ meta .writeVLong (saveNodes (index ));
207225 index .writeLong (0L ); // additional 8 bytes for over-reading
208226 meta .writeVLong (index .getFilePointer ());
209227 status = Status .SAVED ;
210228 }
211229
212- void saveNodes (IndexOutput index ) throws IOException {
230+ long saveNodes (IndexOutput index ) throws IOException {
213231 final long startFP = index .getFilePointer ();
214- Deque <Node > stack = new ArrayDeque <>();
215- stack .push (root );
232+ Deque <SaveFrame > stack = new ArrayDeque <>();
233+ SaveFrame rootFrame = new SaveFrame (root );
234+ stack .push (rootFrame );
216235
217236 // Visit and save nodes of this trie in a post-order depth-first traversal.
218237 while (stack .isEmpty () == false ) {
219- Node node = stack .peek ();
220- assert node .fp == -1 ;
238+ SaveFrame frame = stack .peek ();
239+ Node node = frame .node ;
240+ assert frame .fp == -1 ;
221241 assert assertChildrenLabelInOrder (node );
222242
223243 final int childrenNum = node .childrenNum ;
224244
225245 if (childrenNum == 0 ) { // leaf node
226246 assert node .output != null : "leaf nodes should have output." ;
227247
228- node .fp = index .getFilePointer () - startFP ;
248+ frame .fp = index .getFilePointer () - startFP ;
229249 stack .pop ();
250+ if (stack .isEmpty () == false ) {
251+ stack .peek ().addChildFp (frame .fp );
252+ }
230253
231254 // [n bytes] floor data
232255 // [n bytes] output fp
@@ -251,31 +274,34 @@ void saveNodes(IndexOutput index) throws IOException {
251274 // If there are any children have not been saved, push the first one into stack and continue.
252275 // We want to ensure saving children before parent.
253276
254- if (node .savedTo == null ) {
255- node .savedTo = node .firstChild ;
256- stack .push (node .savedTo );
277+ if (frame .savedTo == null ) {
278+ frame .savedTo = node .firstChild ;
279+ stack .push (new SaveFrame ( frame .savedTo ) );
257280 continue ;
258281 }
259- if (node .savedTo .next != null ) {
260- assert node . savedTo . fp >= 0 ;
261- node .savedTo = node .savedTo .next ;
262- stack .push (node .savedTo );
282+ if (frame .savedTo .next != null ) {
283+ assert frame . numChildFps > 0 && frame . childFps [ frame . numChildFps - 1 ] >= 0 ;
284+ frame .savedTo = frame .savedTo .next ;
285+ stack .push (new SaveFrame ( frame .savedTo ) );
263286 continue ;
264287 }
265288
266289 // All children have been written, now it's time to write the parent!
267290
268- assert assertNonLeafNodePreparingSaving ( node );
269- node .fp = index .getFilePointer () - startFP ;
291+ assert assertNonLeafFramePreparingSaving ( frame );
292+ frame .fp = index .getFilePointer () - startFP ;
270293 stack .pop ();
294+ if (stack .isEmpty () == false ) {
295+ stack .peek ().addChildFp (frame .fp );
296+ }
271297
272298 if (childrenNum == 1 ) {
273299
274300 // [n bytes] floor data
275301 // [n bytes] encoded output fp | [n bytes] child fp | [1 byte] label
276302 // [3bit] encoded output fp bytes | [3bit] child fp bytes | [2bit] sign
277303
278- long childDeltaFp = node .fp - node . firstChild . fp ;
304+ long childDeltaFp = frame .fp - frame . childFps [ 0 ] ;
279305 assert childDeltaFp > 0 : "parent node is always written after children: " + childDeltaFp ;
280306 int childFpBytes = bytesRequiredVLong (childDeltaFp );
281307 int encodedOutputFpBytes =
@@ -317,7 +343,7 @@ void saveNodes(IndexOutput index) throws IOException {
317343 assert strategyBytes > 0 && strategyBytes <= 32 ;
318344
319345 // children fps are in order, so the first child's fp is min, then delta is max.
320- long maxChildDeltaFp = node .fp - node . firstChild . fp ;
346+ long maxChildDeltaFp = frame .fp - frame . childFps [ 0 ] ;
321347 assert maxChildDeltaFp > 0 : "parent always written after all children" ;
322348
323349 int childrenFpBytes = bytesRequiredVLong (maxChildDeltaFp );
@@ -353,9 +379,9 @@ void saveNodes(IndexOutput index) throws IOException {
353379 + " actual: "
354380 + (index .getFilePointer () - strategyStartFp );
355381
356- for (Node child = node . firstChild ; child != null ; child = child . next ) {
357- assert node .fp > child . fp : "parent always written after all children" ;
358- writeLongNBytes (node .fp - child . fp , childrenFpBytes , index );
382+ for (int i = 0 ; i < frame . numChildFps ; i ++ ) {
383+ assert frame .fp > frame . childFps [ i ] : "parent always written after all children" ;
384+ writeLongNBytes (frame .fp - frame . childFps [ i ] , childrenFpBytes , index );
359385 }
360386
361387 if (node .output != null && node .output .floorData != null ) {
@@ -364,6 +390,7 @@ void saveNodes(IndexOutput index) throws IOException {
364390 }
365391 }
366392 }
393+ return rootFrame .fp ;
367394 }
368395
369396 private long encodeFP (Output output ) {
@@ -412,25 +439,28 @@ private static boolean assertChildrenLabelInOrder(Node node) {
412439 return true ;
413440 }
414441
415- private static boolean assertNonLeafNodePreparingSaving (Node node ) {
442+ private static boolean assertNonLeafFramePreparingSaving (SaveFrame frame ) {
443+ Node node = frame .node ;
416444 assert assertChildrenLabelInOrder (node );
417445 assert node .childrenNum != 0 ;
446+ assert frame .numChildFps == node .childrenNum
447+ : "expected " + node .childrenNum + " child fps, got " + frame .numChildFps ;
418448 if (node .childrenNum == 1 ) {
419449 assert node .firstChild == node .lastChild ;
420450 assert node .firstChild .next == null ;
421- assert node .savedTo == node .firstChild ;
422- assert node . firstChild . fp >= 0 ;
451+ assert frame .savedTo == node .firstChild ;
452+ assert frame . childFps [ 0 ] >= 0 ;
423453 } else {
424454 int n = 0 ;
425- for (Node child = node . firstChild ; child != null ; child = child . next ) {
455+ for (int i = 0 ; i < frame . numChildFps ; i ++ ) {
426456 n ++;
427- assert child . fp >= 0 ;
428- assert child . next == null || child . fp < child . next . fp
429- : " the fp or children nodes should always be in order." ;
457+ assert frame . childFps [ i ] >= 0 ;
458+ assert i == frame . numChildFps - 1 || frame . childFps [ i ] < frame . childFps [ i + 1 ]
459+ : " the fp of children nodes should always be in order." ;
430460 }
431461 assert node .childrenNum == n ;
432- assert node .lastChild == node .savedTo ;
433- assert node .savedTo .next == null ;
462+ assert node .lastChild == frame .savedTo ;
463+ assert frame .savedTo .next == null ;
434464 }
435465 return true ;
436466 }
0 commit comments