From 32868995657733eed6fd2e98959a0bacbe2fe29f Mon Sep 17 00:00:00 2001 From: Lars Vogel Date: Fri, 28 Aug 2026 05:57:50 +0200 Subject: [PATCH] Stop building a path for every node of a serialized tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DataTreeWriter and DataTreeReader built an IPath for every node they visited, only to hand it to the flattener. The flatteners only ever asked whether it was the root, whose data holds the parent backpointer and must not be written. This cost a Path plus a segment array copy for every node of every tree in the delta chain, on each save and again on each startup. Pass whether the node is the tree root instead. The reader tracks the depth it needs to recognize a project node. Serializing a tree of 615,000 nodes (600 projects) now allocates 14 MB instead of 56 MB when writing and 73 MB instead of 110 MB when reading. Writing is 12% faster with C2-compiled code, and both directions are about a third faster with C1 only, which is closer to the one-time read at startup. Contributes to https://github.com/eclipse-platform/eclipse.platform/issues/2887 Assisted-by: multiple AI agents and layers of automated tooling 🤖 --- .../core/internal/dtree/DataTreeReader.java | 25 ++++++------------- .../core/internal/dtree/DataTreeWriter.java | 16 ++++++------ .../core/internal/dtree/IDataFlattener.java | 16 +++++------- .../core/internal/resources/SaveManager.java | 8 +++--- .../internal/watson/ElementTreeReader.java | 9 +++---- .../internal/watson/ElementTreeWriter.java | 8 +++--- .../watson/IElementInfoFlattener.java | 14 +++-------- .../ElementTreeSerializationTestHelper.java | 4 +-- 8 files changed, 39 insertions(+), 61 deletions(-) diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java index 38f6142e4ce..49cfce632a0 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeReader.java @@ -18,7 +18,6 @@ import java.io.IOException; import org.eclipse.core.internal.utils.Messages; import org.eclipse.core.runtime.Assert; -import org.eclipse.core.runtime.IPath; /** * Class used for reading a single data tree (no parents) from an input stream @@ -62,25 +61,17 @@ protected boolean hasData(int nodeType) { * the project (first node under root) in the created node * instead of the name read from the stream. */ - protected AbstractDataTreeNode readNode(IPath parentPath, String newProjectName) throws IOException { + protected AbstractDataTreeNode readNode(int depth, String newProjectName) throws IOException { /* read the node name */ String name = input.readUTF(); /* read the node type */ int nodeType = readNumber(); - /* maybe read the data */ - IPath path; - - /* if not the root node */ - if (parentPath != null) { - if (parentPath.equals(IPath.ROOT) && newProjectName.length() > 0 && name.length() > 0) { - /* use the supplied name for the project node */ - name = newProjectName; - } - path = parentPath.append(name); - } else { - path = IPath.ROOT; + /* project nodes are the children of the unnamed root */ + if (depth <= 1 && newProjectName.length() > 0 && name.length() > 0) { + /* use the supplied name for the project node */ + name = newProjectName; } Object data = null; @@ -89,7 +80,7 @@ protected AbstractDataTreeNode readNode(IPath parentPath, String newProjectName) /* read flag indicating if the data is null */ int dataFlag = readNumber(); if (dataFlag != 0) { - data = flatener.readData(path, input); + data = flatener.readData(depth == 0, input); } } @@ -103,7 +94,7 @@ protected AbstractDataTreeNode readNode(IPath parentPath, String newProjectName) } else { children = new AbstractDataTreeNode[childCount]; for (int i = 0; i < childCount; i++) { - children[i] = readNode(path, newProjectName); + children[i] = readNode(depth + 1, newProjectName); } } @@ -147,7 +138,7 @@ protected int readNumber() throws IOException { */ public DeltaDataTree readTree(DeltaDataTree parent, DataInput dataInput, String newProjectName) throws IOException { this.input = dataInput; - AbstractDataTreeNode root = readNode(IPath.ROOT, newProjectName); + AbstractDataTreeNode root = readNode(0, newProjectName); return new DeltaDataTree(root, parent); } } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java index 52175f2a3d3..3987cc0f760 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/DataTreeWriter.java @@ -47,10 +47,10 @@ public DataTreeWriter(IDataFlattener f) { /** * Writes the subtree rooted at the given node. * @param node The subtree to write. - * @param path The path of the current node. + * @param rootNode Whether the node is the root of the tree. * @param depth The depth of the subtree to write. */ - protected void writeNode(AbstractDataTreeNode node, IPath path, int depth) throws IOException { + protected void writeNode(AbstractDataTreeNode node, boolean rootNode, int depth) throws IOException { int type = node.type(); /* write the node name */ @@ -75,7 +75,7 @@ protected void writeNode(AbstractDataTreeNode node, IPath path, int depth) throw writeNumber(0); } else { writeNumber(1); - flatener.writeData(path, node.getData(), output); + flatener.writeData(rootNode, node.getData(), output); } } @@ -90,7 +90,7 @@ protected void writeNode(AbstractDataTreeNode node, IPath path, int depth) throw /* write the children */ int newDepth = (depth == D_INFINITE) ? D_INFINITE : depth - 1; for (AbstractDataTreeNode element : children) { - writeNode(element, path.append(element.getName()), newDepth); + writeNode(element, false, newDepth); } } else { /* write the number of children */ @@ -116,7 +116,7 @@ protected void writeNumber(int number) throws IOException { * Writes a single node to the output. Does not recurse * on child nodes, and does not write the number of children. */ - protected void writeSingleNode(AbstractDataTreeNode node, IPath path) throws IOException { + protected void writeSingleNode(AbstractDataTreeNode node, boolean rootNode) throws IOException { /* write the node name */ String name = node.getName(); if (name == null) { @@ -139,7 +139,7 @@ protected void writeSingleNode(AbstractDataTreeNode node, IPath path) throws IOE writeNumber(0); } else { writeNumber(1); - flatener.writeData(path, node.getData(), output); + flatener.writeData(rootNode, node.getData(), output); } } } @@ -163,7 +163,7 @@ public void writeTree(DeltaDataTree tree, IPath path, int depth, DataOutput data String[] segments = path.segments(); for (String nextSegment : segments) { /* write this node to the output */ - writeSingleNode(node, currentPath); + writeSingleNode(node, currentPath.isRoot()); currentPath = currentPath.append(nextSegment); node = node.childAtOrNull(nextSegment); @@ -181,6 +181,6 @@ public void writeTree(DeltaDataTree tree, IPath path, int depth, DataOutput data Assert.isTrue(currentPath.equals(path), "dtree.navigationError"); //$NON-NLS-1$ /* recursively write the subtree we're interested in */ - writeNode(node, path, depth); + writeNode(node, path.isRoot(), depth); } } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java index e7f31606983..36b22b6e2de 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/dtree/IDataFlattener.java @@ -14,7 +14,6 @@ package org.eclipse.core.internal.dtree; import java.io.*; -import org.eclipse.core.runtime.IPath; /** * The IElementInfoFlattener interface supports @@ -23,20 +22,17 @@ public interface IDataFlattener { /** * Reads a data object from the given input stream. - * @param path the path of the element to be read - * @param input the stream from which the element info should be read. - * @return the object associated with the given path, - * which may be null. + * @param rootNode whether the node being read is the root of the tree + * @return the object read, which may be null. */ - Object readData(IPath path, DataInput input) throws IOException; + Object readData(boolean rootNode, DataInput input) throws IOException; /** * Writes the given data to the output stream. *

N.B. The bytes written must be sufficient for the * purposes of reading the object back in. - * @param path the element's path in the tree - * @param data the object associated with the given path, - * which may be null. + * @param rootNode whether the node being written is the root of the tree + * @param data the object to write, which may be null. */ - void writeData(IPath path, Object data, DataOutput output) throws IOException; + void writeData(boolean rootNode, Object data, DataOutput output) throws IOException; } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java index cb71bffd30a..199e3a1cbbc 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/SaveManager.java @@ -620,11 +620,10 @@ protected boolean isOldPluginTree(String pluginId) { } /** - * @see IElementInfoFlattener#readElement(IPath, DataInput) + * @see IElementInfoFlattener#readElement(DataInput) */ @Override - public Object readElement(IPath path, DataInput input) throws IOException { - Assert.isNotNull(path); + public Object readElement(DataInput input) throws IOException { Assert.isNotNull(input); // read the flags and pull out the type. int flags = input.readInt(); @@ -2013,8 +2012,7 @@ private void writeBuilderPersistentInfo(DataOutputStream output, ListElementTreeReader is the standard implementation * of an element tree serialization reader. @@ -59,16 +58,16 @@ public ElementTreeReader(final IElementInfoFlattener factory) { /* wrap the IElementInfoFlattener in an IDataFlattener */ IDataFlattener f = new IDataFlattener() { @Override - public void writeData(IPath path, Object data, DataOutput output) { + public void writeData(boolean rootNode, Object data, DataOutput output) { //not needed } @Override - public Object readData(IPath path, DataInput input) throws IOException { + public Object readData(boolean rootNode, DataInput input) throws IOException { //never read the root node of an ElementTree //this node is reserved for the parent backpointer - if (!IPath.ROOT.equals(path)) { - return factory.readElement(path, input); + if (!rootNode) { + return factory.readElement(input); } return null; } diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java index 3c47c27c27e..4f7fdbf3a59 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/ElementTreeWriter.java @@ -69,16 +69,16 @@ public ElementTreeWriter(final IElementInfoFlattener flattener) { /* wrap the IElementInfoFlattener in an IDataFlattener */ IDataFlattener f = new IDataFlattener() { @Override - public void writeData(IPath path, Object data, DataOutput output) throws IOException { + public void writeData(boolean rootNode, Object data, DataOutput output) throws IOException { // never write the root node of an ElementTree //because it contains the parent backpointer. - if (!IPath.ROOT.equals(path)) { - flattener.writeElement(path, data, output); + if (!rootNode) { + flattener.writeElement(data, output); } } @Override - public Object readData(IPath path, DataInput input) { + public Object readData(boolean rootNode, DataInput input) { return null; } }; diff --git a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java index a57b76c1894..cc0f3e83bb8 100644 --- a/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java +++ b/resources/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/watson/IElementInfoFlattener.java @@ -14,7 +14,6 @@ package org.eclipse.core.internal.watson; import java.io.*; -import org.eclipse.core.runtime.IPath; /** * The IElementInfoFlattener interface supports @@ -23,20 +22,15 @@ public interface IElementInfoFlattener { /** * Reads an element info from the given input stream. - * @param elementPath the path of the element to be read - * @param input the stream from which the element info should be read. - * @return the object associated with the given elementPath, - * which may be null. + * @return the object read, which may be null. */ - Object readElement(IPath elementPath, DataInput input) throws IOException; + Object readElement(DataInput input) throws IOException; /** * Writes the given element to the output stream. *

N.B. The bytes written must be sufficient for the * purposes of reading the object back in. - * @param elementPath the element's path in the tree - * @param element the object associated with the given path, - * which may be null. + * @param element the object to write, which may be null. */ - void writeElement(IPath elementPath, Object element, DataOutput output) throws IOException; + void writeElement(Object element, DataOutput output) throws IOException; } diff --git a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/watson/ElementTreeSerializationTestHelper.java b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/watson/ElementTreeSerializationTestHelper.java index 72f53e9d1db..a34b218c9f9 100644 --- a/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/watson/ElementTreeSerializationTestHelper.java +++ b/resources/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/watson/ElementTreeSerializationTestHelper.java @@ -198,7 +198,7 @@ public static Object doPipeTest(StreamWriter streamWriter, StreamReader streamRe private static IElementInfoFlattener getFlattener() { return new IElementInfoFlattener() { @Override - public void writeElement(IPath path, Object data, DataOutput output) throws IOException { + public void writeElement(Object data, DataOutput output) throws IOException { if (data == null) { output.writeUTF("null"); } else { @@ -207,7 +207,7 @@ public void writeElement(IPath path, Object data, DataOutput output) throws IOEx } @Override - public Object readElement(IPath path, DataInput input) throws IOException { + public Object readElement(DataInput input) throws IOException { String data = input.readUTF(); if ("null".equals(data)) { return null;