Cache a soft reference to the reconstituted BuildOptions in OptionsDiffForReconstruction.
This makes it so that applyDiff usually does no work and also reduces garbage creation.
Although I do not think the results of applyDiff are currently being compared with equals/hashCode anywhere, this also prevents expensive re-initialization of fingerprint/hashCode if they ever are.
PiperOrigin-RevId: 245148847
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
index ba4e6ac..a64215d 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildOptions.java
@@ -50,6 +50,7 @@
import com.google.protobuf.CodedOutputStream;
import java.io.IOException;
import java.io.Serializable;
+import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -307,7 +308,11 @@
}
maybeInitializeFingerprintAndHashCode();
if (!Arrays.equals(fingerprint, optionsDiff.baseFingerprint)) {
- throw new IllegalArgumentException("Can not reconstruct BuildOptions with a different base.");
+ throw new IllegalArgumentException("Cannot reconstruct BuildOptions with a different base.");
+ }
+ BuildOptions reconstructedOptions = optionsDiff.cachedReconstructed.get();
+ if (reconstructedOptions != null) {
+ return reconstructedOptions;
}
Builder builder = builder();
for (FragmentOptions options : fragmentOptionsMap.values()) {
@@ -332,8 +337,9 @@
}
}
skylarkOptions.putAll(optionsDiff.extraSecondStarlarkOptions);
- builder.addStarlarkOptions(skylarkOptions);
- return builder.build();
+ reconstructedOptions = builder.addStarlarkOptions(skylarkOptions).build();
+ optionsDiff.cachedReconstructed = new SoftReference<>(reconstructedOptions);
+ return reconstructedOptions;
}
/**
@@ -673,7 +679,8 @@
second.computeChecksum(),
diff.skylarkSecond,
diff.extraStarlarkOptionsFirst,
- diff.extraStarlarkOptionsSecond);
+ diff.extraStarlarkOptionsSecond,
+ second);
}
/**
@@ -827,7 +834,15 @@
private final List<Label> extraFirstStarlarkOptions;
private final Map<Label, Object> extraSecondStarlarkOptions;
- @VisibleForTesting
+ /**
+ * A soft reference to the reconstructed build options to save work and garbage creation in
+ * {@link #applyDiff}.
+ *
+ * <p>Promotes reuse of a single {@code BuildOptions} instance to preserve reference equality
+ * and limit fingerprint/hashCode initialization.
+ */
+ private SoftReference<BuildOptions> cachedReconstructed;
+
public OptionsDiffForReconstruction(
Map<Class<? extends FragmentOptions>, Map<String, Object>> differingOptions,
ImmutableSet<Class<? extends FragmentOptions>> extraFirstFragmentClasses,
@@ -836,7 +851,8 @@
String checksum,
Map<Label, Object> differingStarlarkOptions,
List<Label> extraFirstStarlarkOptions,
- Map<Label, Object> extraSecondStarlarkOptions) {
+ Map<Label, Object> extraSecondStarlarkOptions,
+ @Nullable BuildOptions original) {
this.differingOptions = differingOptions;
this.extraFirstFragmentClasses = extraFirstFragmentClasses;
this.extraSecondFragments = extraSecondFragments;
@@ -845,6 +861,7 @@
this.differingStarlarkOptions = differingStarlarkOptions;
this.extraFirstStarlarkOptions = extraFirstStarlarkOptions;
this.extraSecondStarlarkOptions = extraSecondStarlarkOptions;
+ this.cachedReconstructed = new SoftReference<>(original);
}
private static OptionsDiffForReconstruction getEmpty(byte[] baseFingerprint, String checksum) {
@@ -856,7 +873,8 @@
checksum,
ImmutableMap.of(),
ImmutableList.of(),
- ImmutableMap.of());
+ ImmutableMap.of(),
+ /*original=*/ null);
}
@Nullable
@@ -970,6 +988,15 @@
}
}
+ /**
+ * Clears {@link #cachedReconstructed} so that tests can cover the core logic of {@link
+ * #applyDiff}.
+ */
+ @VisibleForTesting
+ void clearCachedReconstructedForTesting() {
+ cachedReconstructed = new SoftReference<>(null);
+ }
+
private boolean hasChangeToStarlarkOptionUnchangedIn(OptionsDiffForReconstruction that) {
Set<Label> starlarkOptionsChangedOrRemovedInThat =
Sets.union(
@@ -1133,7 +1160,8 @@
checksum,
differingStarlarkOptions,
extraFirstStarlarkOptions,
- extraSecondStarlarkOptions);
+ extraSecondStarlarkOptions,
+ /*original=*/ null);
cache.putBytesFromOptionsDiff(diff, bytes);
}
return diff;
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsTest.java b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsTest.java
index 326b2eb..2d97e61 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/config/BuildOptionsTest.java
@@ -45,7 +45,7 @@
* skylark options, the format of this test class will need to accommodate that overlap.
*/
@RunWith(JUnit4.class)
-public class BuildOptionsTest {
+public final class BuildOptionsTest {
private static final ImmutableList<Class<? extends FragmentOptions>> BUILD_CONFIG_OPTIONS =
ImmutableList.of(BuildConfiguration.Options.class);
@@ -87,8 +87,7 @@
BuildOptions.of(BUILD_CONFIG_OPTIONS, options1)
.equals(
BuildOptions.of(
- ImmutableList.<Class<? extends FragmentOptions>>of(
- BuildConfiguration.Options.class, CppOptions.class),
+ ImmutableList.of(BuildConfiguration.Options.class, CppOptions.class),
options1)))
.isFalse();
}
@@ -112,8 +111,7 @@
@Test
public void optionsDiff_differentFragments() throws Exception {
- BuildOptions one =
- BuildOptions.of(ImmutableList.<Class<? extends FragmentOptions>>of(CppOptions.class));
+ BuildOptions one = BuildOptions.of(ImmutableList.of(CppOptions.class));
BuildOptions two = BuildOptions.of(BUILD_CONFIG_OPTIONS);
OptionsDiff diff = BuildOptions.diff(one, two);
@@ -160,7 +158,7 @@
assertThrows(IllegalArgumentException.class, () -> three.applyDiff(diffForReconstruction));
assertThat(e)
.hasMessageThat()
- .contains("Can not reconstruct BuildOptions with a different base");
+ .contains("Cannot reconstruct BuildOptions with a different base");
}
@Test
@@ -177,7 +175,7 @@
public void applyDiff_nativeOptions() throws Exception {
BuildOptions one = BuildOptions.of(BUILD_CONFIG_OPTIONS, "--compilation_mode=opt", "cpu=k8");
BuildOptions two = BuildOptions.of(BUILD_CONFIG_OPTIONS, "--compilation_mode=dbg", "cpu=k8");
- BuildOptions reconstructedTwo = one.applyDiff(BuildOptions.diffForReconstruction(one, two));
+ BuildOptions reconstructedTwo = one.applyDiff(uncachedDiffForReconstruction(one, two));
assertThat(reconstructedTwo).isEqualTo(two);
assertThat(reconstructedTwo).isNotSameAs(two);
BuildOptions reconstructedOne = one.applyDiff(BuildOptions.diffForReconstruction(one, one));
@@ -190,7 +188,7 @@
}
@Test
- public void optionsDiff_sameStarlarkOptions() throws Exception {
+ public void optionsDiff_sameStarlarkOptions() {
Label flagName = Label.parseAbsoluteUnchecked("//foo/flag");
String flagValue = "value";
BuildOptions one = BuildOptions.of(ImmutableMap.of(flagName, flagValue));
@@ -200,7 +198,7 @@
}
@Test
- public void optionsDiff_differentStarlarkOptions() throws Exception {
+ public void optionsDiff_differentStarlarkOptions() {
Label flagName = Label.parseAbsoluteUnchecked("//bar/flag");
String flagValueOne = "valueOne";
String flagValueTwo = "valueTwo";
@@ -218,7 +216,7 @@
}
@Test
- public void optionsDiff_extraStarlarkOptions() throws Exception {
+ public void optionsDiff_extraStarlarkOptions() {
Label flagNameOne = Label.parseAbsoluteUnchecked("//extra/flag/one");
Label flagNameTwo = Label.parseAbsoluteUnchecked("//extra/flag/two");
String flagValue = "foo";
@@ -234,13 +232,13 @@
}
@Test
- public void applyDiff_sameStarlarkOptions() throws Exception {
+ public void applyDiff_sameStarlarkOptions() {
Label flagName = Label.parseAbsoluteUnchecked("//foo/flag");
String flagValue = "value";
BuildOptions one = BuildOptions.of(ImmutableMap.of(flagName, flagValue));
BuildOptions two = BuildOptions.of(ImmutableMap.of(flagName, flagValue));
- BuildOptions reconstructedTwo = one.applyDiff(BuildOptions.diffForReconstruction(one, two));
+ BuildOptions reconstructedTwo = one.applyDiff(uncachedDiffForReconstruction(one, two));
assertThat(reconstructedTwo).isEqualTo(two);
assertThat(reconstructedTwo).isNotSameAs(two);
@@ -251,33 +249,41 @@
}
@Test
- public void applyDiff_differentStarlarkOptions() throws Exception {
+ public void applyDiff_differentStarlarkOptions() {
Label flagName = Label.parseAbsoluteUnchecked("//bar/flag");
String flagValueOne = "valueOne";
String flagValueTwo = "valueTwo";
BuildOptions one = BuildOptions.of(ImmutableMap.of(flagName, flagValueOne));
BuildOptions two = BuildOptions.of(ImmutableMap.of(flagName, flagValueTwo));
- BuildOptions reconstructedTwo = one.applyDiff(BuildOptions.diffForReconstruction(one, two));
+ BuildOptions reconstructedTwo = one.applyDiff(uncachedDiffForReconstruction(one, two));
assertThat(reconstructedTwo).isEqualTo(two);
assertThat(reconstructedTwo).isNotSameAs(two);
}
@Test
- public void applyDiff_extraStarlarkOptions() throws Exception {
+ public void applyDiff_extraStarlarkOptions() {
Label flagNameOne = Label.parseAbsoluteUnchecked("//extra/flag/one");
Label flagNameTwo = Label.parseAbsoluteUnchecked("//extra/flag/two");
String flagValue = "foo";
BuildOptions one = BuildOptions.of(ImmutableMap.of(flagNameOne, flagValue));
BuildOptions two = BuildOptions.of(ImmutableMap.of(flagNameTwo, flagValue));
- BuildOptions reconstructedTwo = one.applyDiff(BuildOptions.diffForReconstruction(one, two));
+ BuildOptions reconstructedTwo = one.applyDiff(uncachedDiffForReconstruction(one, two));
assertThat(reconstructedTwo).isEqualTo(two);
assertThat(reconstructedTwo).isNotSameAs(two);
}
+ @Test
+ public void applyDiff_returnsOriginalOptionsInstanceIfStillExists() throws Exception {
+ BuildOptions base = BuildOptions.of(ImmutableList.of(CppOptions.class), "--compiler=a");
+ BuildOptions original = BuildOptions.of(ImmutableList.of(CppOptions.class), "--compiler=b");
+ BuildOptions reconstructed = base.applyDiff(BuildOptions.diffForReconstruction(base, original));
+ assertThat(reconstructed).isSameAs(original);
+ }
+
private static ImmutableList.Builder<Class<? extends FragmentOptions>> makeOptionsClassBuilder() {
return ImmutableList.<Class<? extends FragmentOptions>>builder()
.addAll(BUILD_CONFIG_OPTIONS)
@@ -334,7 +340,7 @@
}
@Test
- public void testMultiValueOptionImmutability() throws Exception {
+ public void testMultiValueOptionImmutability() {
BuildOptions options =
BuildOptions.of(BUILD_CONFIG_OPTIONS, OptionsParser.newOptionsParser(BUILD_CONFIG_OPTIONS));
BuildConfiguration.Options coreOptions = options.get(Options.class);
@@ -532,4 +538,11 @@
BuildOptions trimmed = original.trim(ImmutableSet.of(CppOptions.class, JavaOptions.class));
assertThat(trimmed).isSameAs(original);
}
+
+ private static OptionsDiffForReconstruction uncachedDiffForReconstruction(
+ BuildOptions first, BuildOptions second) {
+ OptionsDiffForReconstruction diff = BuildOptions.diffForReconstruction(first, second);
+ diff.clearCachedReconstructedForTesting();
+ return diff;
+ }
}