Skip to content

Commit

Permalink
[GR-49568] Improve performance of GraphUtil#killWithFloatingInputs
Browse files Browse the repository at this point in the history
PullRequest: graal/15844
  • Loading branch information
rmosaner committed Nov 30, 2023
2 parents d69a3d3 + 38c0f06 commit d7335f1
Show file tree
Hide file tree
Showing 5 changed files with 302 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.graal.compiler.microbenchmarks.graal;

import org.openjdk.jmh.annotations.Benchmark;

import jdk.graal.compiler.java.FrameStateBuilder;
import jdk.graal.compiler.microbenchmarks.graal.util.GraalState;
import jdk.graal.compiler.microbenchmarks.graal.util.GraphState;
import jdk.graal.compiler.microbenchmarks.graal.util.MethodSpec;
import jdk.graal.compiler.nodes.FrameState;
import jdk.graal.compiler.nodes.ReturnNode;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.util.GraphUtil;
import jdk.graal.compiler.phases.OptimisticOptimizations;
import jdk.graal.compiler.phases.PhaseSuite;
import jdk.graal.compiler.phases.tiers.HighTierContext;
import jdk.vm.ci.meta.JavaKind;

public class KillWithUnusedFloatingInputsBenchmark extends GraalBenchmark {

@Benchmark
public void killWithUnusedFloatingInputsTinyBenchmark(TinyGraph s) {
s.graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()).forEach(GraphUtil::killWithUnusedFloatingInputs);
}

@Benchmark
public void killAllWithUnusedFloatingInputsTinyBenchmark(TinyGraph s) {
GraphUtil.killAllWithUnusedFloatingInputs(s.graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()), false);
}

@Benchmark
public void killWithUnusedFloatingInputsSmallBenchmark(SmallGraph s) {
s.graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()).forEach(GraphUtil::killWithUnusedFloatingInputs);
}

@Benchmark
public void killAllWithUnusedFloatingInputsSmallBenchmark(SmallGraph s) {
GraphUtil.killAllWithUnusedFloatingInputs(s.graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()), false);
}

@Benchmark
public void killWithUnusedFloatingInputsMediumBenchmark(MediumGraph s) {
s.graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()).forEach(GraphUtil::killWithUnusedFloatingInputs);
}

@Benchmark
public void killAllWithUnusedFloatingInputsMediumBenchmark(MediumGraph s) {
GraphUtil.killAllWithUnusedFloatingInputs(s.graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()), false);
}

// too long running
public void killWithUnusedFloatingInputsLargeBenchmark(LargeGraph s) {
s.graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()).forEach(GraphUtil::killWithUnusedFloatingInputs);
}

// too long running
public void killAllWithUnusedFloatingInputsLargeBenchmark(LargeGraph s) {
GraphUtil.killAllWithUnusedFloatingInputs(s.graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()), false);
}

public static int snippet(int base, int mul) {
// the base graph which is later enlarged
return base * mul;
}

@MethodSpec(declaringClass = KillWithUnusedFloatingInputsBenchmark.class, name = "snippet")
public static class TinyGraph extends EnlargedGraph {
public TinyGraph() {
super(1);
}
}

@MethodSpec(declaringClass = KillWithUnusedFloatingInputsBenchmark.class, name = "snippet")
public static class SmallGraph extends EnlargedGraph {
public SmallGraph() {
super(10);
}
}

@MethodSpec(declaringClass = KillWithUnusedFloatingInputsBenchmark.class, name = "snippet")
public static class MediumGraph extends EnlargedGraph {
public MediumGraph() {
super(100);
}
}

@MethodSpec(declaringClass = KillWithUnusedFloatingInputsBenchmark.class, name = "snippet")
public static class LargeGraph extends EnlargedGraph {
public LargeGraph() {
super(1000);
}
}

public static class EnlargedGraph extends GraphState {
private final int sizeParam;

public EnlargedGraph(int sizeParam) {
this.sizeParam = sizeParam;
modifyGraph(originalGraph);
}

@Override
protected StructuredGraph preprocessOriginal(StructuredGraph structuredGraph) {
compileUntilModification(structuredGraph);
return structuredGraph;
}

private static void compileUntilModification(StructuredGraph g) {
GraalState graal = new GraalState();
PhaseSuite<HighTierContext> highTier = graal.backend.getSuites().getDefaultSuites(graal.options, graal.backend.getTarget().arch).getHighTier();
highTier.apply(g, new HighTierContext(graal.providers, graal.backend.getSuites().getDefaultGraphBuilderSuite(), OptimisticOptimizations.ALL));
}

private void modifyGraph(StructuredGraph g) {
ReturnNode ret = g.getNodes(ReturnNode.TYPE).first();
ValueNode val = ret.result();

/*
* Creates "sizeParam" FrameStates without usages. Each of the these FrameStates has
* "sizeParam" inputs.
*/
FrameStateBuilder fb = new FrameStateBuilder(null, g.method(), g);
ValueNode[] vals = new ValueNode[sizeParam];
JavaKind[] kinds = new JavaKind[sizeParam];
for (int i = 0; i < sizeParam; i++) {
vals[i] = val;
kinds[i] = JavaKind.Int;
}

for (int j = 0; j < sizeParam; j++) {
FrameState fs = fb.create(1, null, false, kinds, vals);
g.addOrUnique(fs);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected StructuredGraph preprocessOriginal(StructuredGraph structuredGraph) {
/**
* Original graph from which the per-benchmark invocation {@link #graph} is cloned.
*/
private final StructuredGraph originalGraph;
protected final StructuredGraph originalGraph;

/**
* The graph processed by the benchmark.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.graalvm.collections.EconomicSet;

import jdk.graal.compiler.core.common.Fields;
import jdk.graal.compiler.core.common.type.AbstractPointerStamp;
import jdk.graal.compiler.core.common.type.Stamp;
Expand Down Expand Up @@ -418,7 +420,7 @@ public Iterable<Position> successorPositions() {
}

/**
* Gets the maximum number of usages {@code this} has had at any point in time.
* Gets the current number of usages {@code this} node has.
*/
public int getUsageCount() {
if (usage0 == null) {
Expand Down Expand Up @@ -558,31 +560,83 @@ private void movUsageFromEndToIndexOne() {
* Removes one occurrence of a given node from this node's {@linkplain #usages() usages}.
*
* @param node the node to remove
* @return whether or not {@code usage} was in the usage list
*/
public boolean removeUsage(Node node) {
assert node != null;
// For large graphs, usage removal is performance critical.
// Furthermore, it is critical that this method maintains the invariant that the usage list
// has no null element preceding a non-null element.
incUsageModCount();
if (usage0 == node) {
movUsageFromEndToIndexZero();
incUsageModCount();
return true;
}
if (usage1 == node) {
movUsageFromEndToIndexOne();
incUsageModCount();
return true;
}
for (int i = this.extraUsagesCount - 1; i >= 0; i--) {
if (extraUsages[i] == node) {
movUsageFromEndToExtraUsages(i);
incUsageModCount();
return true;
}
}
return false;
}

/**
* Removes all nodes in the provided set from {@code this} node's usages. This is significantly
* faster than repeated execution of {@link Node#removeUsage}.
*/
public void removeUsages(EconomicSet<Node> toDelete) {
if (toDelete.size() == 0) {
return;
} else if (toDelete.size() == 1) {
removeUsage(toDelete.iterator().next());
return;
}

// requires iteration from back to front to check nodes prior to being moved to the front
for (int i = extraUsagesCount - 1; i >= 0; i--) {
if (toDelete.contains(extraUsages[i])) {
movUsageFromEndToExtraUsages(i);
incUsageModCount();
}
}
if (usage1 != null && toDelete.contains(usage1)) {
movUsageFromEndToIndexOne();
incUsageModCount();
}
if (usage0 != null && toDelete.contains(usage0)) {
movUsageFromEndToIndexZero();
incUsageModCount();
}
}

/**
* Removes all dead nodes from {@code this} node's usages. This is significantly faster than
* repeated execution of {@link Node#removeUsage}.
*/
public void removeDeadUsages() {
// requires iteration from back to front to check nodes prior to being moved to the front
for (int i = extraUsagesCount - 1; i >= 0; i--) {
if (!extraUsages[i].isAlive()) {
movUsageFromEndToExtraUsages(i);
incUsageModCount();
}
}
if (usage1 != null && !usage1.isAlive()) {
movUsageFromEndToIndexOne();
incUsageModCount();
}
if (usage0 != null && !usage0.isAlive()) {
movUsageFromEndToIndexZero();
incUsageModCount();
}
}

public final Node predecessor() {
return predecessor;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,89 @@ public static void killWithUnusedFloatingInputs(Node node, boolean mayKillGuard)
// input is found
}

/**
* Deletes the specified nodes in the graph and all nodes transitively, which are left without
* usages (except {@link GuardNode}s). This implementation is more efficient than repeated
* invocations of {@link GraphUtil#killWithUnusedFloatingInputs(Node, boolean)}.
*/
public static void killAllWithUnusedFloatingInputs(NodeIterable<? extends Node> toKill, boolean mayKillGuard) {
/*
* Removing single nodes from a node's usage list is expensive (especially for complex
* graphs). This implementation works in the following:
*
* 1) If node x uses node y and y is deleted, the deletion of y from x's usages is deferred
* until it is known that x is not deleted as well. Thus, if both x any y are deleted, the
* usage is not deleted.
*
* 2) Nodes which were not deleted during the transitive deletion have their dead usages
* removed all at once, which yields linear complexity for removing multiple usages for one
* node.
*/

// tracks the usage counts for each node instead of actually deleting usages for now
EconomicMap<Node, Integer> usageMap = EconomicMap.create();

EconomicSet<Node> maybeKill = EconomicSet.create();

// delete the initial set of nodes to be killed
for (Node n : toKill) {
assert checkKill(n, mayKillGuard);
n.markDeleted();
for (Node in : n.inputs()) {
Integer usages = usageMap.get(in);
if (usages == null) {
usages = in.getUsageCount();
}
usageMap.put(in, usages - 1);
maybeKill.add(in);
}
}

// fixed point algorithm for transitive deletion of nodes which have no more usages
EconomicSet<Node> newMaybeKill;
do {
newMaybeKill = EconomicSet.create();

for (Node n : maybeKill) {
if (n.isAlive()) {
if (usageMap.get(n) == 0) {
n.maybeNotifyZeroUsages(n);
if (!isFloatingNode(n)) {
continue;
}
if (n instanceof GuardNode) {
// Guard nodes are only killed if their anchor dies.
continue;
}
} else if (n instanceof PhiNode phi && phi.isDegenerated()) {
n.replaceAtUsages(null);
n.maybeNotifyZeroUsages(n);
} else {
continue;
}
// no "checkKill" because usages were not actually removed
n.markDeleted();
for (Node in : n.inputs()) {
Integer usages = usageMap.get(in);
if (usages == null) {
usages = in.usages().count();
}
usageMap.put(in, usages - 1);
newMaybeKill.add(in);
}
}
}
maybeKill = newMaybeKill;
} while (!newMaybeKill.isEmpty());

// actual removal of deleted usages for each node which is still alive
for (Node n : usageMap.getKeys()) {
if (n.isAlive() && (n.getUsageCount() != usageMap.get(n))) {
n.removeDeadUsages();
}
}
}

public static void removeFixedWithUnusedInputs(FixedWithNextNode fixed) {
if (fixed instanceof StateSplit) {
FrameState stateAfter = ((StateSplit) fixed).stateAfter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public Optional<NotApplicable> notApplicableTo(GraphState graphState) {
protected void run(StructuredGraph graph) {
assert !hasFloatingDeopts(graph);
ReentrantNodeIterator.apply(new FrameStateAssignmentClosure(), graph.start(), null);
graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()).forEach(GraphUtil::killWithUnusedFloatingInputs);
GraphUtil.killAllWithUnusedFloatingInputs(graph.getNodes(FrameState.TYPE).filter(state -> state.hasNoUsages()), false);
}

@Override
Expand Down

0 comments on commit d7335f1

Please sign in to comment.