Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix inline compilation benchmarks and Improve searching Graph.Link performance #9520

Merged
merged 7 commits into from
Mar 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.enso.compiler.Compiler;
import org.enso.compiler.benchmarks.Utils;
import org.enso.compiler.context.InlineContext;
import org.graalvm.polyglot.Context;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
Expand Down Expand Up @@ -46,19 +46,18 @@ public class InlineCompilerBenchmark {
private final OutputStream out = new ByteArrayOutputStream();
private Compiler compiler;
private Context ctx;
private InlineContext mainInlineContext;
private InlineSource inlineSource;
private String longExpression;
private Set<String> localVarNames;

@Setup
public void setup() throws IOException {
ctx = Utils.createDefaultContextBuilder().out(out).err(out).logHandler(out).build();
var ensoCtx = Utils.leakEnsoContext(ctx);
compiler = ensoCtx.getCompiler();

var inlineSource = InlineContextUtils.createMainMethodWithLocalVars(ctx, LOCAL_VARS_CNT);
mainInlineContext = inlineSource.mainInlineContext();
longExpression =
InlineContextUtils.createLongExpression(inlineSource.localVarNames(), LONG_EXPR_SIZE);
localVarNames = InlineContextUtils.localVarNames(LOCAL_VARS_CNT);
longExpression = InlineContextUtils.createLongExpression(localVarNames, LONG_EXPR_SIZE);
inlineSource = InlineContextUtils.createMainMethodWithLocalVars(ctx, localVarNames);
}

@TearDown
Expand All @@ -75,11 +74,13 @@ public void tearDown() {
}

@Benchmark
public void longExpression(Blackhole blackhole) {
var tuppleOpt = compiler.runInline(longExpression, mainInlineContext);
if (tuppleOpt.isEmpty()) {
throw new AssertionError("Unexpected: inline compilation should succeed");
public void longExpression(Blackhole blackhole) throws IOException {
try (InlineContextResource resource = inlineSource.inlineContextFactory().create()) {
var tuppleOpt = compiler.runInline(longExpression, resource.inlineContext());
if (tuppleOpt.isEmpty()) {
throw new AssertionError("Unexpected: inline compilation should succeed");
}
blackhole.consume(tuppleOpt);
}
blackhole.consume(tuppleOpt);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import java.util.concurrent.TimeUnit;
import org.enso.compiler.Compiler;
import org.enso.compiler.benchmarks.Utils;
import org.enso.compiler.context.InlineContext;
import org.enso.polyglot.RuntimeOptions;
import org.graalvm.polyglot.Context;
import org.openjdk.jmh.annotations.Benchmark;
Expand Down Expand Up @@ -48,7 +47,7 @@ public class InlineCompilerErrorBenchmark {
private Compiler compiler;

private Context ctx;
private InlineContext mainInlineContext;
private InlineContextResourceFactory mainInlineContextResourceFactory;
private String expressionWithErrors;

@Setup
Expand All @@ -63,11 +62,12 @@ public void setup() throws IOException {
var ensoCtx = Utils.leakEnsoContext(ctx);
compiler = ensoCtx.getCompiler();

var inlineSource = InlineContextUtils.createMainMethodWithLocalVars(ctx, LOCAL_VARS_CNT);
var localVarNames = InlineContextUtils.localVarNames(LOCAL_VARS_CNT);
var inlineSource = InlineContextUtils.createMainMethodWithLocalVars(ctx, localVarNames);
var longExpression =
InlineContextUtils.createLongExpression(inlineSource.localVarNames(), LONG_EXPR_SIZE);
expressionWithErrors = "UNDEFINED * " + longExpression + " * UNDEFINED";
mainInlineContext = inlineSource.mainInlineContext();
mainInlineContextResourceFactory = inlineSource.inlineContextFactory();
}

@TearDown
Expand All @@ -79,8 +79,10 @@ public void teardown() {
}

@Benchmark
public void expressionWithErrors(Blackhole blackhole) {
var tuppleOpt = compiler.runInline(expressionWithErrors, mainInlineContext);
blackhole.consume(tuppleOpt);
public void expressionWithErrors(Blackhole blackhole) throws IOException {
try (InlineContextResource resource = mainInlineContextResourceFactory.create()) {
var tuppleOpt = compiler.runInline(expressionWithErrors, resource.inlineContext());
blackhole.consume(tuppleOpt);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.enso.compiler.benchmarks.inline;

import java.io.IOException;
import org.enso.compiler.context.InlineContext;

/**
* InlineContextResource ensures that the underlying InlineContext is properly cleaned up after
* usage.
*
* @param inlineContext InlineContext for the main method
*/
public record InlineContextResource(InlineContext inlineContext) implements AutoCloseable {
@Override
public void close() throws IOException {
inlineContext
.localScope()
.foreach(
s -> {
s.scope().removeScopeFromParent();
return null;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.enso.compiler.benchmarks.inline;

import org.enso.compiler.PackageRepository;
import org.enso.compiler.context.InlineContext;
import org.enso.interpreter.node.MethodRootNode;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.data.Type;
import org.enso.interpreter.runtime.scope.ModuleScope;

public record InlineContextResourceFactory(
ModuleScope moduleScope,
Type assocTypeReceiver,
EnsoContext ensoCtx,
PackageRepository pkgRepository) {

public InlineContextResource create() {
var mainFunc = moduleScope.getMethodForType(assocTypeReceiver, "main");
var mainFuncRootNode = (MethodRootNode) mainFunc.getCallTarget().getRootNode();
var mainLocalScope = mainFuncRootNode.getLocalScope();
return new InlineContextResource(
InlineContext.fromJava(
mainLocalScope.createChild(),
moduleScope.getModule().asCompilerModule(),
scala.Option.apply(false),
ensoCtx.getCompilerConfig(),
scala.Option.apply(pkgRepository)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import java.util.Set;
import org.enso.compiler.benchmarks.CodeGenerator;
import org.enso.compiler.benchmarks.Utils;
import org.enso.compiler.context.InlineContext;
import org.enso.interpreter.node.MethodRootNode;
import org.enso.interpreter.runtime.data.Type;
import org.enso.polyglot.LanguageInfo;
import org.enso.polyglot.MethodNames;
Expand All @@ -16,25 +14,32 @@
class InlineContextUtils {
private InlineContextUtils() {}

static Set<String> localVarNames(int localVarsCnt) {
Set<String> localVarNames = new HashSet<>();

for (int i = 0; i < localVarsCnt; i++) {
var varName = "loc_var_" + i;
localVarNames.add(varName);
}
return localVarNames;
}

/**
* Creates a main method, generates some local variables, and fills their identifiers in the given
* set.
*
* @param localVarsCnt How many local variables should be initialized in the main method
* @return Body of the main method
*/
static InlineSource createMainMethodWithLocalVars(Context ctx, int localVarsCnt)
static InlineSource createMainMethodWithLocalVars(Context ctx, Set<String> localVarNames)
throws IOException {
var sb = new StringBuilder();
sb.append("main = ").append(System.lineSeparator());
var codeGen = new CodeGenerator();
Set<String> localVarNames = new HashSet<>();
for (int i = 0; i < localVarsCnt; i++) {
var varName = "loc_var_" + i;
localVarNames.add(varName);
for (String localVarName : localVarNames) {
var literal = codeGen.nextLiteral();
sb.append(" ")
.append(varName)
.append(localVarName)
.append(" = ")
.append(literal)
.append(System.lineSeparator());
Expand All @@ -51,18 +56,11 @@ static InlineSource createMainMethodWithLocalVars(Context ctx, int localVarsCnt)
var moduleAssocType = module.invokeMember(MethodNames.Module.GET_ASSOCIATED_TYPE);
var assocTypeReceiver = (Type) Utils.unwrapReceiver(ctx, moduleAssocType);
var moduleScope = assocTypeReceiver.getDefinitionScope();
var mainFunc = moduleScope.getMethodForType(assocTypeReceiver, "main");
var mainFuncRootNode = (MethodRootNode) mainFunc.getCallTarget().getRootNode();
var mainLocalScope = mainFuncRootNode.getLocalScope();
var compiler = ensoCtx.getCompiler();
var mainInlineContext =
InlineContext.fromJava(
mainLocalScope,
moduleScope.getModule().asCompilerModule(),
scala.Option.apply(false),
ensoCtx.getCompilerConfig(),
scala.Option.apply(compiler.packageRepository()));
return new InlineSource(sb.toString(), mainInlineContext, localVarNames);
var inlineCtxMeta =
new InlineContextResourceFactory(
moduleScope, assocTypeReceiver, ensoCtx, compiler.packageRepository());
return new InlineSource(sb.toString(), inlineCtxMeta, localVarNames);
}

static String createLongExpression(Set<String> localVars, int exprSize) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package org.enso.compiler.benchmarks.inline;

import java.util.Set;
import org.enso.compiler.context.InlineContext;

record InlineSource(
String source,
// InlineContext for the main method
InlineContext mainInlineContext,
// InlineContextResource for the main method
InlineContextResourceFactory inlineContextFactory,
// Local variables in main method
Set<String> localVarNames) {}
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ protected Graph readObject(Input in) throws IOException {

var links =
(scala.collection.immutable.Set) in.readInline(scala.collection.immutable.Set.class);
g.links_$eq(links);
g.initLinks(links);

var nextIdCounter = in.readInt();
g.nextIdCounter_$eq(nextIdCounter);
Expand All @@ -178,7 +178,7 @@ protected Graph readObject(Input in) throws IOException {
@Override
protected void writeObject(Graph obj, Output out) throws IOException {
out.writeObject(obj.rootScope());
out.writeInline(scala.collection.immutable.Set.class, obj.links());
out.writeInline(scala.collection.immutable.Set.class, obj.getLinks());
out.writeInt(obj.nextIdCounter());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import org.enso.syntax.text.Debug
import org.enso.compiler.pass.analyse.alias.Graph.{Occurrence, Scope}

import java.util.UUID
import scala.collection.immutable.HashMap
import scala.collection.mutable
import scala.reflect.ClassTag

/** A graph containing aliasing information for a given root scope in Enso. */
sealed class Graph extends Serializable {
var rootScope: Graph.Scope = new Graph.Scope()
var links: Set[Graph.Link] = Set()
var nextIdCounter = 0
var rootScope: Graph.Scope = new Graph.Scope()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If initXyz is better pattern than calling g.rootScope_$eq(rootScope); - then rootScope could be made private as well, possibly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried and looks like a lot more stuff depends on it being public. I tried to keep the PR succinct for now.

private var links: Set[Graph.Link] = Set()
private var sourceLinks: Map[Graph.Id, Set[Graph.Link]] = new HashMap()
private var targetLinks: Map[Graph.Id, Set[Graph.Link]] = new HashMap()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an implementation of serialization logic for the Graph

@org.openide.util.lookup.ServiceProvider(service = Persistance.class)
public static final class PersistAliasAnalysisGraph extends Persistance<Graph> {
public PersistAliasAnalysisGraph() {
super(Graph.class, false, 1268);
}

Please make sure that it works correctly with the newly added fields and probably update the serialization id

Copy link
Collaborator Author

@hubertp hubertp Mar 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should work correctly as I'm reproducing sourceLinks and targetLinks from links when deserialized. I'm not serializing those fields as there is no need for that (links continues to be serialized).

var nextIdCounter = 0

private var globalSymbols: Map[Graph.Symbol, Occurrence.Global] =
Map()
Expand All @@ -24,11 +27,22 @@ sealed class Graph extends Serializable {
val copy = new Graph
copy.rootScope = this.rootScope.deepCopy(scope_mapping)
copy.links = this.links
copy.sourceLinks = this.sourceLinks
copy.targetLinks = this.targetLinks
copy.globalSymbols = this.globalSymbols
copy.nextIdCounter = this.nextIdCounter
copy
}

def initLinks(links: Set[Graph.Link]): Unit = {
hubertp marked this conversation as resolved.
Show resolved Hide resolved
sourceLinks = new HashMap()
targetLinks = new HashMap()
links.foreach(addSourceTargetLink)
this.links = links
}

def getLinks(): Set[Graph.Link] = links
Copy link
Member

@JaroslavTulach JaroslavTulach Mar 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to remove the links field all together and just compute the links from sourceLinks and targetLinks? The logic between initLinks and getLinks & co. would be simpler and possibly getLinks() isn't on critical path. Otherwise the inconsistency must have been caught somewhere, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of like that links is the source of truth and sourceLinks/targetLinks serve as aux indexes for quicker access. It also simplifies serde.


/** Registers a requested global symbol in the aliasing scope.
*
* @param sym the symbol occurrence
Expand All @@ -46,6 +60,8 @@ sealed class Graph extends Serializable {
def copy: Graph = {
val graph = new Graph
graph.links = links
graph.sourceLinks = sourceLinks
graph.targetLinks = targetLinks
graph.rootScope = rootScope.deepCopy(mutable.Map())
graph.nextIdCounter = nextIdCounter

Expand Down Expand Up @@ -85,10 +101,20 @@ sealed class Graph extends Serializable {
): Option[Graph.Link] = {
scopeFor(occurrence.id).flatMap(_.resolveUsage(occurrence).map { link =>
links += link
addSourceTargetLink(link)
link
})
}

private def addSourceTargetLink(link: Graph.Link): Unit = {
sourceLinks = sourceLinks.updatedWith(link.source)(v =>
v.map(s => s + link).orElse(Some(Set(link)))
)
targetLinks = targetLinks.updatedWith(link.target)(v =>
v.map(s => s + link).orElse(Some(Set(link)))
)
}

/** Resolves any links for the given usage of a symbol, assuming the symbol
* is global (i.e. method, constructor etc.)
*
Expand Down Expand Up @@ -129,7 +155,10 @@ sealed class Graph extends Serializable {
* @return a list of links in which `id` occurs
*/
def linksFor(id: Graph.Id): Set[Graph.Link] = {
links.filter(l => l.source == id || l.target == id)
sourceLinks.getOrElse(id, Set.empty[Graph.Link]) ++ targetLinks.getOrElse(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great finding. Good fix. Thank you.

id,
Set()
)
}

/** Finds all links in the graph where `symbol` appears in the role
Expand Down Expand Up @@ -646,6 +675,17 @@ object Graph {

isDirectChildOf || isChildOfChildren
}

private def removeScopeFromParent(scope: Scope): Unit = {
childScopes = childScopes.filter(_ != scope)
hubertp marked this conversation as resolved.
Show resolved Hide resolved
}

/** Disassociates this Scope from its parent.
*/
def removeScopeFromParent(): Unit = {
assert(this.parent.nonEmpty)
this.parent.foreach(_.removeScopeFromParent(this))
}
}

/** A link in the [[Graph]].
Expand Down
Loading
Loading