Save space when serializing ArtifactRoot: serialize the base exec root as a likely constant after https://github.com/bazelbuild/bazel/commit/f93c72c34e1d1d3211c834f2bec432eed47b763a and reconstruct this root from the execPath on deserialization.
PiperOrigin-RevId: 303997590
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ArtifactRoot.java b/src/main/java/com/google/devtools/build/lib/actions/ArtifactRoot.java
index 6eba8cf..4ad8ae6 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ArtifactRoot.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ArtifactRoot.java
@@ -89,8 +89,13 @@
@AutoCodec.VisibleForSerialization
@AutoCodec.Instantiator
- static ArtifactRoot createForSerialization(Root root, PathFragment execPath, RootType rootType) {
- return INTERNER.intern(new ArtifactRoot(root, execPath, rootType));
+ static ArtifactRoot createForSerialization(
+ Root rootForSerialization, PathFragment execPath, RootType rootType) {
+ if (rootType != RootType.Output) {
+ return INTERNER.intern(new ArtifactRoot(rootForSerialization, execPath, rootType));
+ }
+ return asDerivedRoot(
+ rootForSerialization.asPath(), execPath.getSegments().toArray(new String[0]));
}
@AutoCodec.VisibleForSerialization
@@ -149,6 +154,31 @@
return Objects.hash(root, execPath, rootType);
}
+ /**
+ * The Root of a derived ArtifactRoot contains the exec path. In order to avoid duplicating that
+ * path, and enable the Root to be serialized as a constant, we return the "exec root" Root here,
+ * by stripping the exec path. That Root is likely to be serialized as a constant by {@link
+ * Root.RootCodec}, saving a lot of serialized bytes on the wire.
+ */
+ @SuppressWarnings("unused") // Used by @AutoCodec.
+ Root getRootForSerialization() {
+ if (rootType != RootType.Output) {
+ return root;
+ }
+ // Find fragment of root that does not include execPath and return just that root. It is likely
+ // to be serialized as a constant by RootCodec. For instance, if the original exec root was
+ // /execroot, and this root was /execroot/bazel-out/bin, with execPath bazel-out/bin, then we
+ // just serialize /execroot and bazel-out/bin separately.
+ // We just want to strip execPath from root, but I don't know a trivial way to do that.
+ PathFragment rootFragment = root.asPath().asFragment();
+ return Root.fromPath(
+ root.asPath()
+ .getFileSystem()
+ .getPath(
+ rootFragment.subFragment(
+ 0, rootFragment.segmentCount() - execPath.segmentCount())));
+ }
+
@Override
public boolean equals(Object o) {
if (o == this) {
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ArtifactRootTest.java b/src/test/java/com/google/devtools/build/lib/actions/ArtifactRootTest.java
index 6ecb16c..6df05ff 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ArtifactRootTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ArtifactRootTest.java
@@ -16,11 +16,18 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import com.google.common.collect.ImmutableMap;
import com.google.common.testing.EqualsTester;
+import com.google.devtools.build.lib.skyframe.serialization.AutoRegistry;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodecRegistry;
+import com.google.devtools.build.lib.skyframe.serialization.ObjectCodecs;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
import com.google.devtools.build.lib.testutil.Scratch;
+import com.google.devtools.build.lib.vfs.FileSystem;
import com.google.devtools.build.lib.vfs.Path;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.devtools.build.lib.vfs.Root;
+import com.google.protobuf.ByteString;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,6 +96,28 @@
}
@Test
+ public void derivedSerialization() throws IOException, SerializationException {
+ Path execRoot = scratch.dir("/thisisaveryverylongexecrootthatwedontwanttoserialize");
+ ArtifactRoot derivedRoot = ArtifactRoot.asDerivedRoot(execRoot, "first", "second", "third");
+ ObjectCodecRegistry registry = AutoRegistry.get();
+ ImmutableMap<Class<?>, Object> dependencies =
+ ImmutableMap.<Class<?>, Object>builder()
+ .put(FileSystem.class, scratch.getFileSystem())
+ .put(
+ Root.RootCodecDependencies.class,
+ new Root.RootCodecDependencies(/*likelyPopularRoot=*/ Root.fromPath(execRoot)))
+ .build();
+ ObjectCodecRegistry.Builder registryBuilder = registry.getBuilder();
+ for (Object val : dependencies.values()) {
+ registryBuilder.addReferenceConstant(val);
+ }
+ ObjectCodecs objectCodecs = new ObjectCodecs(registryBuilder.build(), dependencies);
+ ByteString serialized = objectCodecs.serialize(derivedRoot);
+ // 28 bytes as of 2020/03/31.
+ assertThat(serialized.size()).isLessThan(30);
+ }
+
+ @Test
public void testEquals() throws IOException {
Path execRoot = scratch.dir("/exec");
String rootSegment = "root";
@@ -104,7 +133,7 @@
false, rootA, ArtifactRoot.asDerivedRoot(otherRootDir, "exec", rootSegment));
}
- public void assertEqualsAndHashCode(boolean expected, Object a, Object b) {
+ private void assertEqualsAndHashCode(boolean expected, Object a, Object b) {
if (expected) {
new EqualsTester().addEqualityGroup(b, a).testEquals();
} else {