Optimize TransitiveInfoMap memory consumption.
Instead of using ImmutableMap, we share the keys between all provider maps with an identical key set.
PiperOrigin-RevId: 155432135
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
index dc74887..be92473 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredAspect.java
@@ -36,7 +36,6 @@
import com.google.devtools.build.lib.syntax.EvalException;
import com.google.devtools.build.lib.util.Preconditions;
import java.util.Arrays;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
@@ -46,20 +45,19 @@
* Extra information about a configured target computed on request of a dependent.
*
* <p>Analogous to {@link ConfiguredTarget}: contains a bunch of transitive info providers, which
- * are merged with the providers of the associated configured target before they are passed to
- * the configured target factories that depend on the configured target to which this aspect is
- * added.
+ * are merged with the providers of the associated configured target before they are passed to the
+ * configured target factories that depend on the configured target to which this aspect is added.
*
* <p>Aspects are created alongside configured targets on request from dependents.
*
- * <p>For more information about aspects, see
- * {@link com.google.devtools.build.lib.packages.AspectClass}.
+ * <p>For more information about aspects, see {@link
+ * com.google.devtools.build.lib.packages.AspectClass}.
*
* @see com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory
* @see com.google.devtools.build.lib.packages.AspectClass
*/
@Immutable
-public final class ConfiguredAspect implements Iterable<TransitiveInfoProvider> {
+public final class ConfiguredAspect {
private final TransitiveInfoProviderMap providers;
private final AspectDescriptor descriptor;
@@ -119,12 +117,6 @@
: null;
}
-
- @Override
- public Iterator<TransitiveInfoProvider> iterator() {
- return providers.values().iterator();
- }
-
public static ConfiguredAspect forAlias(ConfiguredAspect real) {
return new ConfiguredAspect(real.descriptor, real.getProviders());
}
@@ -188,9 +180,10 @@
/** Adds providers to the aspect. */
public Builder addProviders(TransitiveInfoProviderMap providers) {
- for (Map.Entry<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> entry :
- providers.entries()) {
- addProvider(entry.getKey(), entry.getKey().cast(entry.getValue()));
+ for (int i = 0; i < providers.getProviderCount(); ++i) {
+ Class<? extends TransitiveInfoProvider> providerClass = providers.getProviderClassAt(i);
+ TransitiveInfoProvider provider = providers.getProviderAt(i);
+ addProvider(providerClass, providerClass.cast(provider));
}
return this;
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/MergedConfiguredTarget.java b/src/main/java/com/google/devtools/build/lib/analysis/MergedConfiguredTarget.java
index 10f5cae..a9079ad 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/MergedConfiguredTarget.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/MergedConfiguredTarget.java
@@ -17,7 +17,6 @@
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
/**
* A single dependency with its configured target and aspects merged together.
@@ -90,9 +89,9 @@
}
for (ConfiguredAspect aspect : aspects) {
- for (Map.Entry<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> entry :
- aspect.getProviders().entries()) {
- Class<? extends TransitiveInfoProvider> providerClass = entry.getKey();
+ TransitiveInfoProviderMap providers = aspect.getProviders();
+ for (int i = 0; i < providers.getProviderCount(); ++i) {
+ Class<? extends TransitiveInfoProvider> providerClass = providers.getProviderClassAt(i);
if (OutputGroupProvider.class.equals(providerClass)
|| SkylarkProviders.class.equals(providerClass)
|| ExtraActionArtifactsProvider.class.equals(providerClass)) {
@@ -103,7 +102,7 @@
throw new IllegalStateException("Provider " + providerClass + " provided twice");
}
- aspectProviders.add(entry.getValue());
+ aspectProviders.add(providers.getProviderAt(i));
}
}
return new MergedConfiguredTarget(base, aspectProviders.build());
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
index 8fa2c4e..84fa027 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleConfiguredTargetBuilder.java
@@ -146,11 +146,11 @@
/** Adds skylark providers from a skylark provider registry, and checks for collisions. */
private void addRegisteredProvidersToSkylarkProviders(TransitiveInfoProviderMap providers) {
Map<String, Object> nativeSkylarkProviders = new HashMap<>();
- for (Map.Entry<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> entry :
- providers.entries()) {
- if (ruleContext.getSkylarkProviderRegistry().containsValue(entry.getKey())) {
- String skylarkName = ruleContext.getSkylarkProviderRegistry().inverse().get(entry.getKey());
- nativeSkylarkProviders.put(skylarkName, entry.getValue());
+ for (int i = 0; i < providers.getProviderCount(); ++i) {
+ Class<? extends TransitiveInfoProvider> providerClass = providers.getProviderClassAt(i);
+ if (ruleContext.getSkylarkProviderRegistry().containsValue(providerClass)) {
+ String skylarkName = ruleContext.getSkylarkProviderRegistry().inverse().get(providerClass);
+ nativeSkylarkProviders.put(skylarkName, providers.getProviderAt(i));
}
}
try {
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMap.java b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMap.java
index b824bb2..96f9cb7 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMap.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMap.java
@@ -14,7 +14,6 @@
package com.google.devtools.build.lib.analysis;
-import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
@@ -25,7 +24,9 @@
@Nullable
<P extends TransitiveInfoProvider> P getProvider(Class<P> providerClass);
- Iterable<Map.Entry<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>> entries();
+ int getProviderCount();
- Iterable<TransitiveInfoProvider> values();
+ Class<? extends TransitiveInfoProvider> getProviderClassAt(int i);
+
+ TransitiveInfoProvider getProviderAt(int i);
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapBuilder.java b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapBuilder.java
index ac68e3b..06a7726 100644
--- a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapBuilder.java
@@ -55,7 +55,10 @@
}
public TransitiveInfoProviderMapBuilder addAll(TransitiveInfoProviderMap providers) {
- return addAll(providers.values());
+ for (int i = 0; i < providers.getProviderCount(); ++i) {
+ add(providers.getProviderAt(i));
+ }
+ return this;
}
public TransitiveInfoProviderMapBuilder addAll(Iterable<TransitiveInfoProvider> providers) {
@@ -71,6 +74,6 @@
}
public TransitiveInfoProviderMap build() {
- return new TransitiveInfoProviderMapImmutableMapBased(ImmutableMap.copyOf(providers));
+ return new TransitiveInfoProviderMapOffsetBased(ImmutableMap.copyOf(providers));
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapImmutableMapBased.java b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapImmutableMapBased.java
deleted file mode 100644
index f87ca57..0000000
--- a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapImmutableMapBased.java
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2014 The Bazel Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.devtools.build.lib.analysis;
-
-import com.google.common.collect.ImmutableMap;
-import java.util.Map;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.Immutable;
-
-/** Provides a mapping between a TransitiveInfoProvider class and an instance. */
-@Immutable
-final class TransitiveInfoProviderMapImmutableMapBased implements TransitiveInfoProviderMap {
-
- private final ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> map;
-
- TransitiveInfoProviderMapImmutableMapBased(
- ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> map) {
- this.map = map;
- }
-
- /** Returns the instance for the provided providerClass, or <tt>null</tt> if not present. */
- @Override
- @Nullable
- public <P extends TransitiveInfoProvider> P getProvider(Class<P> providerClass) {
- return (P) map.get(TransitiveInfoProviderEffectiveClassHelper.get(providerClass));
- }
-
- @Override
- public Iterable<Map.Entry<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider>>
- entries() {
- return map.entrySet();
- }
-
- @Override
- public Iterable<TransitiveInfoProvider> values() {
- return map.values();
- }
-}
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapOffsetBased.java b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapOffsetBased.java
new file mode 100644
index 0000000..a36a3e1
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/analysis/TransitiveInfoProviderMapOffsetBased.java
@@ -0,0 +1,127 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.analysis;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Interner;
+import com.google.devtools.build.lib.concurrent.BlazeInterners;
+import java.util.Arrays;
+import java.util.Map.Entry;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Provides a mapping between a TransitiveInfoProvider class and an instance.
+ *
+ * <p>This class implements a map where it is expected that a lot of the key sets will be the same.
+ * These key sets are shared and an offset table of indices is computed. Each provider map instance
+ * thus contains only a reference to the shared offset table, and a plain array of providers.
+ */
+@Immutable
+final class TransitiveInfoProviderMapOffsetBased implements TransitiveInfoProviderMap {
+ private static final Interner<OffsetTable> offsetTables = BlazeInterners.newWeakInterner();
+
+ private final OffsetTable offsetTable;
+ private final TransitiveInfoProvider[] providers;
+
+ private static final class OffsetTable {
+ private final Class<? extends TransitiveInfoProvider>[] providerClasses;
+ // Keep a map around to speed up get lookups for larger maps.
+ // We make this value lazy to avoid computing for values that end up being thrown away
+ // during interning anyway (the majority).
+ private volatile ImmutableMap<Class<? extends TransitiveInfoProvider>, Integer> indexMap;
+
+ OffsetTable(Class<? extends TransitiveInfoProvider>[] providerClasses) {
+ this.providerClasses = providerClasses;
+ }
+
+ private ImmutableMap<Class<? extends TransitiveInfoProvider>, Integer> getIndexMap() {
+ if (indexMap == null) {
+ synchronized (this) {
+ if (indexMap == null) {
+ ImmutableMap.Builder<Class<? extends TransitiveInfoProvider>, Integer> builder =
+ ImmutableMap.builder();
+ for (int i = 0; i < providerClasses.length; ++i) {
+ builder.put(providerClasses[i], i);
+ }
+ this.indexMap = builder.build();
+ }
+ }
+ }
+ return indexMap;
+ }
+
+ int getOffsetForClass(Class effectiveClass) {
+ return getIndexMap().getOrDefault(effectiveClass, -1);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof OffsetTable)) {
+ return false;
+ }
+ OffsetTable that = (OffsetTable) o;
+ return Arrays.equals(this.providerClasses, that.providerClasses);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(providerClasses);
+ }
+ }
+
+ TransitiveInfoProviderMapOffsetBased(
+ ImmutableMap<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> map) {
+ int count = map.size();
+ Class<? extends TransitiveInfoProvider>[] providerClasses = new Class[count];
+ this.providers = new TransitiveInfoProvider[count];
+ int i = 0;
+ for (Entry<Class<? extends TransitiveInfoProvider>, TransitiveInfoProvider> entry :
+ map.entrySet()) {
+ providerClasses[i] = entry.getKey();
+ providers[i] = entry.getValue();
+ ++i;
+ }
+ OffsetTable offsetTable = new OffsetTable(providerClasses);
+ this.offsetTable = offsetTables.intern(offsetTable);
+ }
+
+ /** Returns the instance for the provided providerClass, or <tt>null</tt> if not present. */
+ @Override
+ @Nullable
+ public <P extends TransitiveInfoProvider> P getProvider(Class<P> providerClass) {
+ Class effectiveClass = TransitiveInfoProviderEffectiveClassHelper.get(providerClass);
+ int offset = offsetTable.getOffsetForClass(effectiveClass);
+ return offset >= 0 ? (P) providers[offset] : null;
+ }
+
+ @Override
+ public int getProviderCount() {
+ return providers.length;
+ }
+
+ @Override
+ public Class<? extends TransitiveInfoProvider> getProviderClassAt(int i) {
+ return offsetTable.providerClasses[i];
+ }
+
+ @Override
+ public TransitiveInfoProvider getProviderAt(int i) {
+ return providers[i];
+ }
+}