Cache BuildOptions.diffForReconstruction.
PiperOrigin-RevId: 245323196
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 a64215d..ad6bf3c 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
@@ -17,6 +17,8 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -63,6 +65,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
@@ -629,11 +632,40 @@
}
/**
+ * Cache for {@link OptionsDiffForReconstruction}, which is expensive to compute.
+ *
+ * <p>The reason for using {@linkplain CacheBuilder#weakKeys weak keys} is twofold: we want
+ * objects in the cache to be garbage collected, and we also want to use reference equality to
+ * avoid the expensive initialization in {@link #maybeInitializeFingerprintAndHashCode}.
+ */
+ private static final Cache<BuildOptions, OptionsDiffForReconstruction>
+ diffForReconstructionCache = CacheBuilder.newBuilder().weakKeys().build();
+
+ /**
* Returns a {@link OptionsDiffForReconstruction} object that can be applied to {@code first} via
* {@link #applyDiff} to get a {@link BuildOptions} object equal to {@code second}.
*/
public static OptionsDiffForReconstruction diffForReconstruction(
BuildOptions first, BuildOptions second) {
+ OptionsDiffForReconstruction diff;
+ try {
+ diff =
+ diffForReconstructionCache.get(second, () -> createDiffForReconstruction(first, second));
+ } catch (ExecutionException e) {
+ throw new IllegalStateException(e);
+ }
+
+ // We need to ensure that the possibly cached diff was computed against the same base options.
+ // In practice this should always be the case, since callers pass in a "default" options
+ // instance as "first". To be safe however, we create an uncached diff if there is a mismatch.
+ // Note that this check should be fast because the fingerprints should be reference-equal.
+ return Arrays.equals(first.fingerprint, diff.baseFingerprint)
+ ? diff
+ : createDiffForReconstruction(first, second);
+ }
+
+ private static OptionsDiffForReconstruction createDiffForReconstruction(
+ BuildOptions first, BuildOptions second) {
OptionsDiff diff = diff(first, second);
if (diff.areSame()) {
first.maybeInitializeFingerprintAndHashCode();
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 2d97e61..0b7d95f 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
@@ -284,6 +284,31 @@
assertThat(reconstructed).isSameAs(original);
}
+ @Test
+ public void diffForReconstruction_calledTwiceWithSameArgs_returnsSameInstance() throws Exception {
+ BuildOptions one = BuildOptions.of(ImmutableList.of(CppOptions.class), "--compiler=one");
+ BuildOptions two = BuildOptions.of(ImmutableList.of(CppOptions.class), "--compiler=two");
+
+ OptionsDiffForReconstruction diff1 = BuildOptions.diffForReconstruction(one, two);
+ OptionsDiffForReconstruction diff2 = BuildOptions.diffForReconstruction(one, two);
+
+ assertThat(diff1).isSameAs(diff2);
+ }
+
+ @Test
+ public void diffForReconstruction_againstDifferentBase() throws Exception {
+ BuildOptions base1 = BuildOptions.of(ImmutableList.of(CppOptions.class), "--compiler=base1");
+ BuildOptions base2 = BuildOptions.of(ImmutableList.of(CppOptions.class), "--compiler=base2");
+ BuildOptions other = BuildOptions.of(ImmutableList.of(CppOptions.class), "--compiler=other");
+
+ OptionsDiffForReconstruction diff1 = BuildOptions.diffForReconstruction(base1, other);
+ OptionsDiffForReconstruction diff2 = BuildOptions.diffForReconstruction(base2, other);
+
+ assertThat(diff1).isNotEqualTo(diff2);
+ assertThat(base1.applyDiff(diff1)).isEqualTo(other);
+ assertThat(base2.applyDiff(diff2)).isEqualTo(other);
+ }
+
private static ImmutableList.Builder<Class<? extends FragmentOptions>> makeOptionsClassBuilder() {
return ImmutableList.<Class<? extends FragmentOptions>>builder()
.addAll(BUILD_CONFIG_OPTIONS)