Track changed and dirtied keys separately during invalidation

Reduces the number of allocations during invalidation. Has a magnified
effect when optional support for interruptibility is turned off.

--
MOS_MIGRATED_REVID=108978068
diff --git a/src/main/java/com/google/devtools/build/skyframe/InvalidatingNodeVisitor.java b/src/main/java/com/google/devtools/build/skyframe/InvalidatingNodeVisitor.java
index 2e012e9..2b23b92 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InvalidatingNodeVisitor.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InvalidatingNodeVisitor.java
@@ -20,7 +20,6 @@
 import com.google.common.collect.ImmutableList.Builder;
 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 com.google.devtools.build.lib.concurrent.AbstractQueueVisitor;
 import com.google.devtools.build.lib.concurrent.ErrorClassifier;
@@ -33,7 +32,6 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -161,8 +159,6 @@
     return executor.getInterruptionLatchForTestingOnly();
   }
 
-  protected abstract long count();
-
   protected void informInvalidationReceiver(SkyKey key,
       EvaluationProgressReceiver.InvalidationState state) {
     if (invalidationReceiver != null) {
@@ -249,11 +245,6 @@
     }
 
     @Override
-    protected long count() {
-      return visited.size();
-    }
-
-    @Override
     protected boolean getSupportInterruptions() {
       return true;
     }
@@ -340,9 +331,13 @@
   /** A node-dirtying implementation. */
   static class DirtyingNodeVisitor extends InvalidatingNodeVisitor<ThinNodeQueryableGraph> {
 
-    private final Set<Pair<SkyKey, InvalidationType>> visited =
+    private final Set<SkyKey> changed =
         Collections.newSetFromMap(
-            new ConcurrentHashMap<Pair<SkyKey, InvalidationType>, Boolean>(
+            new ConcurrentHashMap<SkyKey, Boolean>(
+                EXPECTED_VISITED_SET_SIZE, .75f, DEFAULT_THREAD_COUNT));
+    private final Set<SkyKey> dirtied =
+        Collections.newSetFromMap(
+            new ConcurrentHashMap<SkyKey, Boolean>(
                 EXPECTED_VISITED_SET_SIZE, .75f, DEFAULT_THREAD_COUNT));
     private final boolean supportInterruptions;
 
@@ -372,11 +367,6 @@
     }
 
     @Override
-    protected long count() {
-      return visited.size();
-    }
-
-    @Override
     protected boolean getSupportInterruptions() {
       return supportInterruptions;
     }
@@ -398,7 +388,7 @@
      * {@link NodeEntry} ignores the second marking.
      *
      * The invariant that we do not process a (SkyKey, InvalidationType) pair twice is enforced by
-     * the {@link #visited} set.
+     * the {@link #changed} and {@link #dirtied} sets.
      *
      * The "invariant" is also enforced across builds by checking to see if the entry is already
      * marked changed, or if it is already marked dirty and we are just going to mark it dirty
@@ -409,30 +399,29 @@
      */
     @Override
     @ThreadSafe
-    public void visit(Iterable<SkyKey> keys, final InvalidationType invalidationType,
-        final boolean mustExist) {
+    public void visit(
+        Iterable<SkyKey> keys, final InvalidationType invalidationType, final boolean mustExist) {
       Preconditions.checkState(invalidationType != InvalidationType.DELETED, keys);
       final boolean isChanged = (invalidationType == InvalidationType.CHANGED);
+      Set<SkyKey> setToCheck = isChanged ? changed : dirtied;
       int size = Iterables.size(keys);
-      ArrayList<Pair<SkyKey, InvalidationType>> invalidationPairs = new ArrayList<>(size);
+      ArrayList<SkyKey> keysToGet = new ArrayList<>(size);
       for (SkyKey key : keys) {
-        Pair<SkyKey, InvalidationType> invalidationPair = Pair.of(key, invalidationType);
-        if (visited.add(invalidationPair)) {
-          invalidationPairs.add(invalidationPair);
+        if (setToCheck.add(key)) {
+          keysToGet.add(key);
         }
       }
-      List<SkyKey> keysToGet =
-          Lists.transform(invalidationPairs, Pair.<SkyKey, InvalidationType>firstFunction());
       if (supportInterruptions) {
-        pendingVisitations.addAll(invalidationPairs);
+        for (SkyKey key : keysToGet) {
+          pendingVisitations.add(Pair.of(key, invalidationType));
+        }
       }
       final Map<SkyKey, ? extends ThinNodeEntry> entries = graph.getBatch(keysToGet);
-      for (final Pair<SkyKey, InvalidationType> invalidationPair : invalidationPairs) {
+      for (final SkyKey key : keysToGet) {
         executor.execute(
             new Runnable() {
               @Override
               public void run() {
-                SkyKey key = invalidationPair.getFirst();
                 ThinNodeEntry entry = entries.get(key);
 
                 if (entry == null) {
@@ -442,7 +431,7 @@
                           + "node",
                       key);
                   if (supportInterruptions) {
-                    pendingVisitations.remove(invalidationPair);
+                    pendingVisitations.remove(Pair.of(key, invalidationType));
                   }
                   return;
                 }
@@ -451,7 +440,7 @@
                   // If this node is already marked changed, or we are only marking this node
                   // dirty, and it already is, move along.
                   if (supportInterruptions) {
-                    pendingVisitations.remove(invalidationPair);
+                    pendingVisitations.remove(Pair.of(key, invalidationType));
                   }
                   return;
                 }
@@ -464,7 +453,7 @@
                 if (markedDirtyResult == null) {
                   // Another thread has already dirtied this node. Don't do anything in this thread.
                   if (supportInterruptions) {
-                    pendingVisitations.remove(invalidationPair);
+                    pendingVisitations.remove(Pair.of(key, invalidationType));
                   }
                   return;
                 }
@@ -477,7 +466,7 @@
                 dirtyKeyTracker.dirty(key);
                 // Remove the node from the set as the last operation.
                 if (supportInterruptions) {
-                  pendingVisitations.remove(invalidationPair);
+                  pendingVisitations.remove(Pair.of(key, invalidationType));
                 }
               }
             });