aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtilImpl.java
diff options
context:
space:
mode:
authorGravatar Janak Ramakrishnan <janakr@google.com>2015-11-23 18:51:55 +0000
committerGravatar Philipp Wollermann <philwo@google.com>2015-11-24 14:41:06 +0000
commit4f487f4a4d27504965c2ecdc965d17e7ba846db5 (patch)
tree24a8a2ca8a66ff2a5879e09e1b6ce8aec9fb4851 /src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtilImpl.java
parent18212f10f2bbb6a29c5c1f112e0a43b92071b11c (diff)
Extract ReverseDepsUtil interface so that InMemoryNodeEntry can be partially isolated from implementation details.
-- MOS_MIGRATED_REVID=108523104
Diffstat (limited to 'src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtilImpl.java')
-rw-r--r--src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtilImpl.java394
1 files changed, 394 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtilImpl.java b/src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtilImpl.java
new file mode 100644
index 0000000000..11af1005a7
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtilImpl.java
@@ -0,0 +1,394 @@
+// 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.skyframe;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Interner;
+import com.google.common.collect.Interners;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.collect.CompactHashSet;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * A utility class that allows us to keep the reverse dependencies as an array list instead of a
+ * set. This is more memory-efficient. At the same time it allows us to group the removals and
+ * uniqueness checks so that it also performs well.
+ *
+ * <p>The reason for making this a separate class is to share non-trivial code between BuildingState
+ * and NodeEntry. We could simply make those two classes extend this class instead, but we would be
+ * less memory-efficient since object memory alignment does not cross classes (you would have two
+ * memory alignments, one for the base class and one for the extended one).
+ *
+ */
+public abstract class ReverseDepsUtilImpl<T> implements ReverseDepsUtil<T> {
+
+ static final int MAYBE_CHECK_THRESHOLD = 10;
+
+ private static final Interner<KeyToConsolidate> consolidateInterner = Interners.newWeakInterner();
+
+ abstract void setReverseDepsObject(T container, Object object);
+
+ abstract void setSingleReverseDep(T container, boolean singleObject);
+
+ abstract void setDataToConsolidate(T container, @Nullable List<Object> dataToConsolidate);
+
+ abstract Object getReverseDepsObject(T container);
+
+ abstract boolean isSingleReverseDep(T container);
+
+ abstract List<Object> getDataToConsolidate(T container);
+
+ private enum ConsolidateOp {
+ CHECK,
+ ADD,
+ REMOVE
+ }
+
+ /**
+ * Opaque container for a pending operation on the reverse deps set. We use subclasses to save
+ * 8 bytes of memory instead of keeping a field in this class, and we store
+ * {@link ConsolidateOp#CHECK} operations as the bare {@link SkyKey} in order to save the wrapper
+ * object in that case.
+ */
+ private abstract static class KeyToConsolidate {
+ // Do not access directly -- use the {@link #key} static accessor instead.
+ protected final SkyKey key;
+
+ /** Do not call directly -- use the {@link #create} static method instead. */
+ private KeyToConsolidate(SkyKey key) {
+ this.key = key;
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this).add("key", key).toString();
+ }
+
+ /** Gets which operation was delayed for the given object. */
+ static ConsolidateOp op(Object obj) {
+ if (obj instanceof SkyKey) {
+ return ConsolidateOp.CHECK;
+ }
+ if (obj instanceof KeyToRemove) {
+ return ConsolidateOp.REMOVE;
+ }
+ Preconditions.checkState(obj instanceof KeyToAdd, obj);
+ return ConsolidateOp.ADD;
+ }
+
+ /** Gets the key whose operation was delayed for the given object. */
+ static SkyKey key(Object obj) {
+ if (obj instanceof SkyKey) {
+ return (SkyKey) obj;
+ }
+ Preconditions.checkState(obj instanceof KeyToConsolidate, obj);
+ return ((KeyToConsolidate) obj).key;
+ }
+
+ static Object create(SkyKey key, ConsolidateOp op) {
+ switch (op) {
+ case CHECK:
+ return key;
+ case REMOVE:
+ return consolidateInterner.intern(new KeyToRemove(key));
+ case ADD:
+ return consolidateInterner.intern(new KeyToAdd(key));
+ default:
+ throw new IllegalStateException(op + ", " + key);
+ }
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ return this.getClass() == obj.getClass() && this.key.equals(((KeyToConsolidate) obj).key);
+ }
+
+ @Override
+ public int hashCode() {
+ // Overridden in subclasses.
+ throw new UnsupportedOperationException(key.toString());
+ }
+ }
+
+ private static final class KeyToAdd extends KeyToConsolidate {
+ KeyToAdd(SkyKey key) {
+ super(key);
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode();
+ }
+ }
+
+ private static final class KeyToRemove extends KeyToConsolidate {
+ KeyToRemove(SkyKey key) {
+ super(key);
+ }
+
+ @Override
+ public int hashCode() {
+ return 42 + 37 * key.hashCode();
+ }
+ }
+
+ private void maybeDelayReverseDepOp(T container, Iterable<SkyKey> reverseDeps, ConsolidateOp op) {
+ List<Object> consolidations = getDataToConsolidate(container);
+ int currentReverseDepSize = getCurrentReverseDepSize(container);
+ if (consolidations == null) {
+ consolidations = new ArrayList<>(currentReverseDepSize);
+ setDataToConsolidate(container, consolidations);
+ }
+ for (SkyKey reverseDep : reverseDeps) {
+ consolidations.add(KeyToConsolidate.create(reverseDep, op));
+ }
+ // TODO(janakr): Should we consolidate more aggressively? This threshold can be customized.
+ if (consolidations.size() == currentReverseDepSize) {
+ consolidateData(container);
+ }
+ }
+
+ /**
+ * We only check if reverse deps is small and there are no delayed data to consolidate, since
+ * then presence or absence would not be known.
+ */
+ @Override
+ public void maybeCheckReverseDepNotPresent(T container, SkyKey reverseDep) {
+ if (getDataToConsolidate(container) != null) {
+ return;
+ }
+ if (isSingleReverseDep(container)) {
+ Preconditions.checkState(
+ !getReverseDepsObject(container).equals(reverseDep),
+ "Reverse dep %s already present in %s",
+ reverseDep,
+ container);
+ return;
+ }
+ @SuppressWarnings("unchecked")
+ List<SkyKey> asList = (List<SkyKey>) getReverseDepsObject(container);
+ if (asList.size() < MAYBE_CHECK_THRESHOLD) {
+ Preconditions.checkState(
+ !asList.contains(reverseDep),
+ "Reverse dep %s already present in %s for %s",
+ reverseDep,
+ asList,
+ container);
+ }
+ }
+
+ private int getCurrentReverseDepSize(T container) {
+ return isSingleReverseDep(container)
+ ? 1
+ : ((List<SkyKey>) getReverseDepsObject(container)).size();
+ }
+
+ /**
+ * We use a memory-efficient trick to keep reverseDeps memory usage low. Edges in Bazel are
+ * dominant over the number of nodes.
+ *
+ * <p>Most of the nodes have zero or one reverse dep. That is why we use immutable versions of the
+ * lists for those cases. In case of the size being > 1 we switch to an ArrayList. That is because
+ * we also have a decent number of nodes for which the reverseDeps are huge (for example almost
+ * everything depends on BuildInfo node).
+ *
+ * <p>We also optimize for the case where we have only one dependency. In that case we keep the
+ * object directly instead of a wrapper list.
+ */
+ @SuppressWarnings("unchecked")
+ public void addReverseDeps(T container, Collection<SkyKey> newReverseDeps) {
+ if (newReverseDeps.isEmpty()) {
+ return;
+ }
+ List<Object> dataToConsolidate = getDataToConsolidate(container);
+ if (dataToConsolidate != null) {
+ maybeDelayReverseDepOp(container, newReverseDeps, ConsolidateOp.ADD);
+ return;
+ }
+ Object reverseDeps = getReverseDepsObject(container);
+ int reverseDepsSize = isSingleReverseDep(container) ? 1 : ((List<SkyKey>) reverseDeps).size();
+ int newSize = reverseDepsSize + newReverseDeps.size();
+ if (newSize == 1) {
+ overwriteReverseDepsWithObject(container, Iterables.getOnlyElement(newReverseDeps));
+ } else if (reverseDepsSize == 0) {
+ overwriteReverseDepsList(container, Lists.newArrayList(newReverseDeps));
+ } else if (reverseDepsSize == 1) {
+ List<SkyKey> newList = Lists.newArrayListWithExpectedSize(newSize);
+ newList.add((SkyKey) reverseDeps);
+ newList.addAll(newReverseDeps);
+ overwriteReverseDepsList(container, newList);
+ } else {
+ ((List<SkyKey>) reverseDeps).addAll(newReverseDeps);
+ }
+ }
+
+ @Override
+ public void checkReverseDep(T container, SkyKey reverseDep) {
+ maybeDelayReverseDepOp(container, ImmutableList.of(reverseDep), ConsolidateOp.CHECK);
+ }
+
+ /**
+ * See {@code addReverseDeps} method.
+ */
+ @Override
+ public void removeReverseDep(T container, SkyKey reverseDep) {
+ maybeDelayReverseDepOp(container, ImmutableList.of(reverseDep), ConsolidateOp.REMOVE);
+ }
+
+ @Override
+ public ImmutableSet<SkyKey> getReverseDeps(T container) {
+ consolidateData(container);
+
+ // TODO(bazel-team): Unfortunately, we need to make a copy here right now to be on the safe side
+ // wrt. thread-safety. The parents of a node get modified when any of the parents is deleted,
+ // and we can't handle that right now.
+ if (isSingleReverseDep(container)) {
+ return ImmutableSet.of((SkyKey) getReverseDepsObject(container));
+ } else {
+ @SuppressWarnings("unchecked")
+ List<SkyKey> reverseDeps = (List<SkyKey>) getReverseDepsObject(container);
+ ImmutableSet<SkyKey> set = ImmutableSet.copyOf(reverseDeps);
+ Preconditions.checkState(
+ set.size() == reverseDeps.size(),
+ "Duplicate reverse deps present in %s: %s. %s",
+ this,
+ reverseDeps,
+ container);
+ return set;
+ }
+ }
+
+ private void consolidateData(T container) {
+ List<Object> dataToConsolidate = getDataToConsolidate(container);
+ if (dataToConsolidate == null) {
+ return;
+ }
+ setDataToConsolidate(container, null);
+ Object reverseDeps = getReverseDepsObject(container);
+ if (isSingleReverseDep(container)) {
+ Preconditions.checkState(
+ dataToConsolidate.size() == 1,
+ "dataToConsolidate not size 1 even though only one rdep: %s %s %s",
+ dataToConsolidate,
+ reverseDeps,
+ container);
+ Object keyToConsolidate = Iterables.getOnlyElement(dataToConsolidate);
+ SkyKey key = KeyToConsolidate.key(keyToConsolidate);
+ switch (KeyToConsolidate.op(keyToConsolidate)) {
+ case REMOVE:
+ overwriteReverseDepsList(container, ImmutableList.<SkyKey>of());
+ // Fall through to check.
+ case CHECK:
+ Preconditions.checkState(
+ key.equals(reverseDeps), "%s %s %s", keyToConsolidate, reverseDeps, container);
+ break;
+ case ADD:
+ throw new IllegalStateException(
+ "Shouldn't delay add if only one element: "
+ + keyToConsolidate
+ + ", "
+ + reverseDeps
+ + ", "
+ + container);
+ default:
+ throw new IllegalStateException(keyToConsolidate + ", " + reverseDeps + ", " + container);
+ }
+ return;
+ }
+ List<SkyKey> reverseDepsAsList = (List<SkyKey>) reverseDeps;
+ Set<SkyKey> reverseDepsAsSet = CompactHashSet.create(reverseDepsAsList);
+
+ if (reverseDepsAsSet.size() != reverseDepsAsList.size()) {
+ // We're about to crash. Try to print an informative error message.
+ Set<SkyKey> seen = new HashSet<>();
+ List<SkyKey> duplicates = new ArrayList<>();
+ for (SkyKey key : reverseDepsAsList) {
+ if (!seen.add(key)) {
+ duplicates.add(key);
+ }
+ }
+ throw new IllegalStateException(
+ (reverseDepsAsList.size() - reverseDepsAsSet.size())
+ + " duplicates: "
+ + duplicates
+ + " for "
+ + container);
+ }
+ for (Object keyToConsolidate : dataToConsolidate) {
+ SkyKey key = KeyToConsolidate.key(keyToConsolidate);
+ switch (KeyToConsolidate.op(keyToConsolidate)) {
+ case CHECK:
+ Preconditions.checkState(
+ reverseDepsAsSet.contains(key),
+ "%s %s %s",
+ keyToConsolidate,
+ reverseDepsAsSet,
+ container);
+ break;
+ case REMOVE:
+ Preconditions.checkState(
+ reverseDepsAsSet.remove(key), "%s %s %s", keyToConsolidate, reverseDeps, container);
+ break;
+ case ADD:
+ Preconditions.checkState(
+ reverseDepsAsSet.add(key), "%s %s %s", keyToConsolidate, reverseDeps, container);
+ break;
+ default:
+ throw new IllegalStateException(
+ keyToConsolidate + ", " + reverseDepsAsSet + ", " + container);
+ }
+ }
+
+ if (reverseDepsAsSet.isEmpty()) {
+ overwriteReverseDepsList(container, ImmutableList.<SkyKey>of());
+ } else if (reverseDepsAsSet.size() == 1) {
+ overwriteReverseDepsWithObject(container, Iterables.getOnlyElement(reverseDepsAsSet));
+ } else {
+ overwriteReverseDepsList(container, new ArrayList<>(reverseDepsAsSet));
+ }
+ }
+
+ @Override
+ public String toString(T container) {
+ return MoreObjects.toStringHelper("ReverseDeps")
+ .add("reverseDeps", getReverseDepsObject(container))
+ .add("singleReverseDep", isSingleReverseDep(container))
+ .add("dataToConsolidate", getDataToConsolidate(container))
+ .toString();
+ }
+
+ private void overwriteReverseDepsWithObject(T container, SkyKey newObject) {
+ setReverseDepsObject(container, newObject);
+ setSingleReverseDep(container, true);
+ }
+
+ private void overwriteReverseDepsList(T container, List<SkyKey> list) {
+ setReverseDepsObject(container, list);
+ setSingleReverseDep(container, false);
+ }
+}