diff options
author | 2015-02-25 16:45:20 +0100 | |
---|---|---|
committer | 2015-02-25 16:45:20 +0100 | |
commit | d08b27fa9701fecfdb69e1b0d1ac2459efc2129b (patch) | |
tree | 5d50963026239ca5aebfb47ea5b8db7e814e57c8 /src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtil.java |
Update from Google.
--
MOE_MIGRATED_REVID=85702957
Diffstat (limited to 'src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtil.java')
-rw-r--r-- | src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtil.java | 211 |
1 files changed, 211 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtil.java b/src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtil.java new file mode 100644 index 0000000000..13d8c4b276 --- /dev/null +++ b/src/main/java/com/google/devtools/build/skyframe/ReverseDepsUtil.java @@ -0,0 +1,211 @@ +// Copyright 2014 Google Inc. 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.Objects; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * 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 of this class it 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). + */ +abstract class ReverseDepsUtil<T> { + + static final int MAYBE_CHECK_THRESHOLD = 10; + + abstract void setReverseDepsObject(T container, Object object); + + abstract void setSingleReverseDep(T container, boolean singleObject); + + abstract void setReverseDepsToRemove(T container, List<SkyKey> object); + + abstract Object getReverseDepsObject(T container); + + abstract boolean isSingleReverseDep(T container); + + abstract List<SkyKey> getReverseDepsToRemove(T container); + + /** + * We check that the reverse dependency is not already present. We only do that if reverseDeps is + * small, so that it does not impact performance. + */ + void maybeCheckReverseDepNotPresent(T container, SkyKey reverseDep) { + if (isSingleReverseDep(container)) { + Preconditions.checkState(!getReverseDepsObject(container).equals(reverseDep), + "Reverse dep %s already present", reverseDep); + 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", reverseDep, asList); + } + } + + /** + * 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") + void addReverseDeps(T container, Collection<SkyKey> newReverseDeps) { + if (newReverseDeps.isEmpty()) { + 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); + } + } + + /** + * See {@code addReverseDeps} method. + */ + void removeReverseDep(T container, SkyKey reverseDep) { + if (isSingleReverseDep(container)) { + // This removal is cheap so let's do it and not keep it in reverseDepsToRemove. + // !equals should only happen in case of catastrophe. + if (getReverseDepsObject(container).equals(reverseDep)) { + overwriteReverseDepsList(container, ImmutableList.<SkyKey>of()); + } + return; + } + @SuppressWarnings("unchecked") + List<SkyKey> reverseDepsAsList = (List<SkyKey>) getReverseDepsObject(container); + if (reverseDepsAsList.isEmpty()) { + return; + } + List<SkyKey> reverseDepsToRemove = getReverseDepsToRemove(container); + if (reverseDepsToRemove == null) { + reverseDepsToRemove = Lists.newArrayListWithExpectedSize(1); + setReverseDepsToRemove(container, reverseDepsToRemove); + } + reverseDepsToRemove.add(reverseDep); + } + + ImmutableSet<SkyKey> getReverseDeps(T container) { + consolidateReverseDepsRemovals(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; + } + } + + void consolidateReverseDepsRemovals(T container) { + List<SkyKey> reverseDepsToRemove = getReverseDepsToRemove(container); + Object reverseDeps = getReverseDepsObject(container); + if (reverseDepsToRemove == null) { + return; + } + Preconditions.checkState(!isSingleReverseDep(container), + "We do not use reverseDepsToRemove for single lists: %s", container); + // Should not happen, as we only create reverseDepsToRemove in case we have at least one + // reverse dep to remove. + Preconditions.checkState((!((List<?>) reverseDeps).isEmpty()), + "Could not remove %s elements from %s.\nReverse deps to remove: %s. %s", + reverseDepsToRemove.size(), reverseDeps, reverseDepsToRemove, container); + + Set<SkyKey> toRemove = Sets.newHashSet(reverseDepsToRemove); + int expectedRemovals = toRemove.size(); + Preconditions.checkState(expectedRemovals == reverseDepsToRemove.size(), + "A reverse dependency tried to remove itself twice: %s. %s", reverseDepsToRemove, + container); + + @SuppressWarnings("unchecked") + List<SkyKey> reverseDepsAsList = (List<SkyKey>) reverseDeps; + List<SkyKey> newReverseDeps = Lists + .newArrayListWithExpectedSize(Math.max(0, reverseDepsAsList.size() - expectedRemovals)); + + for (SkyKey reverseDep : reverseDepsAsList) { + if (!toRemove.contains(reverseDep)) { + newReverseDeps.add(reverseDep); + } + } + Preconditions.checkState(newReverseDeps.size() == reverseDepsAsList.size() - expectedRemovals, + "Could not remove some elements from %s.\nReverse deps to remove: %s. %s", reverseDeps, + toRemove, container); + + if (newReverseDeps.isEmpty()) { + overwriteReverseDepsList(container, ImmutableList.<SkyKey>of()); + } else if (newReverseDeps.size() == 1) { + overwriteReverseDepsWithObject(container, newReverseDeps.get(0)); + } else { + overwriteReverseDepsList(container, newReverseDeps); + } + setReverseDepsToRemove(container, null); + } + + @SuppressWarnings("deprecation") + String toString(T container) { + return Objects.toStringHelper("ReverseDeps") // MoreObjects is not in Guava + .add("reverseDeps", getReverseDepsObject(container)) + .add("singleReverseDep", isSingleReverseDep(container)) + .add("reverseDepsToRemove", getReverseDepsToRemove(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); + } +} |