mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 17:06:48 +03:00
Format Java sources in benchmark configuration (#8638)
I noticed that sources in `runtime/bench` are not formatted at all. Turns out that the `JavaFormatterPlugin` does not override `javafmt` task for the `Benchmark` configuration. After some failed attempts, I have just redefined the `Benchmark/javafmt` task in the `runtime` project. After all, the `runtime` project is almost the only project where we have any Java benchmarks. # Important Notes `javafmtAll` now also formats sources in `runtime/bench/src/java`.
This commit is contained in:
parent
2d628263ff
commit
48a3c14ee5
22
build.sbt
22
build.sbt
@ -1533,6 +1533,28 @@ lazy val runtime = (project in file("engine/runtime"))
|
||||
version := ensoVersion,
|
||||
commands += WithDebugCommand.withDebug,
|
||||
inConfig(Compile)(truffleRunOptionsSettings),
|
||||
// Explicitly provide javafmt task for the custom Benchmark configuration.
|
||||
// Note that because of the custom Benchmark configuration, the `JavaFormatterPlugin`
|
||||
// is not able to register this task on its own.
|
||||
Benchmark / javafmt := {
|
||||
val streamz = streams.value
|
||||
val sD = (Benchmark / javafmt / sourceDirectories).value.toList
|
||||
val iF = (Benchmark / javafmt / includeFilter).value
|
||||
val eF = (Benchmark / javafmt / excludeFilter).value
|
||||
val cache = streamz.cacheStoreFactory
|
||||
val options = (Compile / javafmtOptions).value
|
||||
JavaFormatter(sD, iF, eF, streamz, cache, options)
|
||||
},
|
||||
Benchmark / javafmtCheck := {
|
||||
val streamz = streams.value
|
||||
val baseDir = (ThisBuild / baseDirectory).value
|
||||
val sD = (Benchmark / javafmt / sourceDirectories).value.toList
|
||||
val iF = (Benchmark / javafmt / includeFilter).value
|
||||
val eF = (Benchmark / javafmt / excludeFilter).value
|
||||
val cache = (javafmt / streams).value.cacheStoreFactory
|
||||
val options = (Compile / javafmtOptions).value
|
||||
JavaFormatter.check(baseDir, sD, iF, eF, streamz, cache, options)
|
||||
},
|
||||
Test / parallelExecution := false,
|
||||
Test / logBuffered := false,
|
||||
Test / testOptions += Tests.Argument(
|
||||
|
@ -20,7 +20,9 @@ public class BenchmarkItem {
|
||||
return previousResults;
|
||||
}
|
||||
|
||||
/** @return Best historic score for the given benchmark (including current run). */
|
||||
/**
|
||||
* @return Best historic score for the given benchmark (including current run).
|
||||
*/
|
||||
public double getBestScore() {
|
||||
return previousResults.getBestScore().orElse(result.getScore());
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ import org.openjdk.jmh.runner.options.OptionsBuilder;
|
||||
public class BenchmarksRunner {
|
||||
public static final File REPORT_FILE = new File("./bench-report.xml");
|
||||
|
||||
/** @return A list of qualified names of all benchmarks visible to JMH. */
|
||||
/**
|
||||
* @return A list of qualified names of all benchmarks visible to JMH.
|
||||
*/
|
||||
public List<String> getAvailable() {
|
||||
return BenchmarkList.defaultList().getAll(null, new ArrayList<>()).stream()
|
||||
.map(BenchmarkListEntry::getUsername)
|
||||
@ -32,14 +34,13 @@ public class BenchmarksRunner {
|
||||
* @return a {@link BenchmarkItem} containing current run result and historical results.
|
||||
*/
|
||||
public BenchmarkItem run(String label) throws RunnerException, JAXBException {
|
||||
ChainedOptionsBuilder builder = new OptionsBuilder()
|
||||
.jvmArgsAppend("-Xss16M", "-Dpolyglot.engine.MultiTier=false")
|
||||
.include("^" + label + "$");
|
||||
ChainedOptionsBuilder builder =
|
||||
new OptionsBuilder()
|
||||
.jvmArgsAppend("-Xss16M", "-Dpolyglot.engine.MultiTier=false")
|
||||
.include("^" + label + "$");
|
||||
|
||||
if (Boolean.getBoolean("bench.compileOnly")) {
|
||||
builder
|
||||
.measurementIterations(1)
|
||||
.warmupIterations(0);
|
||||
builder.measurementIterations(1).warmupIterations(0);
|
||||
}
|
||||
|
||||
Options benchmarkOptions = builder.build();
|
||||
|
@ -1,9 +1,5 @@
|
||||
package org.enso.interpreter.bench;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import jakarta.xml.bind.JAXBContext;
|
||||
import jakarta.xml.bind.JAXBException;
|
||||
import jakarta.xml.bind.Marshaller;
|
||||
@ -11,6 +7,10 @@ import jakarta.xml.bind.Unmarshaller;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlElementWrapper;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Historic runs report. Supports XML serialization. */
|
||||
@XmlRootElement
|
||||
|
@ -1,13 +1,13 @@
|
||||
package org.enso.interpreter.bench;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
import jakarta.xml.bind.annotation.XmlElement;
|
||||
import jakarta.xml.bind.annotation.XmlElementWrapper;
|
||||
import jakarta.xml.bind.annotation.XmlRootElement;
|
||||
import jakarta.xml.bind.annotation.XmlTransient;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalDouble;
|
||||
|
||||
/** Contains historic results for a single benchmark identified by label. */
|
||||
@XmlRootElement
|
||||
@ -53,7 +53,9 @@ public class ReportItem {
|
||||
getScores().add(score);
|
||||
}
|
||||
|
||||
/** @return The best (lowest) historic result for this benchmark. */
|
||||
/**
|
||||
* @return The best (lowest) historic result for this benchmark.
|
||||
*/
|
||||
@XmlTransient
|
||||
public Optional<Double> getBestScore() {
|
||||
OptionalDouble min = getScores().stream().mapToDouble(s -> s).min();
|
||||
|
@ -1,11 +1,9 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Engine;
|
||||
@ -38,17 +36,15 @@ public class ArrayProxyBenchmarks {
|
||||
Engine eng =
|
||||
Engine.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.option(
|
||||
RuntimeOptions.LOG_LEVEL,
|
||||
Level.WARNING.getName()
|
||||
)
|
||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath())
|
||||
.build();
|
||||
var ctx = Context.newBuilder().engine(eng).allowIO(IOAccess.ALL).allowAllAccess(true).build();
|
||||
var code = """
|
||||
var code =
|
||||
"""
|
||||
import Standard.Base.Data.Vector.Vector
|
||||
import Standard.Base.Data.Array_Proxy.Array_Proxy
|
||||
sum arr =
|
||||
@ -92,8 +88,7 @@ public class ArrayProxyBenchmarks {
|
||||
test_builder = "make_delegating_vector";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException(
|
||||
"Unexpected benchmark: " + params.getBenchmark());
|
||||
throw new IllegalStateException("Unexpected benchmark: " + params.getBenchmark());
|
||||
}
|
||||
this.arrayOfNumbers = getMethod.apply(test_builder).execute(self, length);
|
||||
this.sum = getMethod.apply("sum");
|
||||
@ -133,8 +128,7 @@ public class ArrayProxyBenchmarks {
|
||||
long expectedResult = length * 3L + (5L * (length * (length - 1L) / 2L));
|
||||
boolean isResultCorrect = result == expectedResult;
|
||||
if (!isResultCorrect) {
|
||||
throw new AssertionError(
|
||||
"Expecting " + expectedResult + " but was " + result);
|
||||
throw new AssertionError("Expecting " + expectedResult + " but was " + result);
|
||||
}
|
||||
matter.consume(result);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.enso.interpreter.bench.fixtures.semantic.CallableFixtures;
|
||||
import org.enso.interpreter.test.DefaultInterpreterRunner;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
@ -18,8 +17,7 @@ import org.openjdk.jmh.annotations.Warmup;
|
||||
@Measurement(iterations = 5)
|
||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||
public class CallableBenchmarks {
|
||||
private static final CallableFixtures argumentFixtures =
|
||||
new CallableFixtures();
|
||||
private static final CallableFixtures argumentFixtures = new CallableFixtures();
|
||||
|
||||
private void runOnHundredMillion(DefaultInterpreterRunner.MainMethod main) {
|
||||
main.mainFunction().value().execute(argumentFixtures.hundredMillion());
|
||||
|
@ -1,6 +1,5 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@ -10,7 +9,6 @@ import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.enso.polyglot.MethodNames.Module;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
@ -30,8 +28,8 @@ import org.openjdk.jmh.infra.BenchmarkParams;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
/**
|
||||
* Benchmarks for `Any.==` method. This benchmark takes two vectors as input, and compares each
|
||||
* pair of elements with `==`.
|
||||
* Benchmarks for `Any.==` method. This benchmark takes two vectors as input, and compares each pair
|
||||
* of elements with `==`.
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@ -43,18 +41,16 @@ public class EqualsBenchmarks {
|
||||
|
||||
private static final int primitiveVectorSize = 4_000;
|
||||
private static final int stringsVectorSize = 3_000;
|
||||
/**
|
||||
* Maximum length of randomly generated strings.
|
||||
*/
|
||||
|
||||
/** Maximum length of randomly generated strings. */
|
||||
private static final int maxStringSize = 20;
|
||||
/**
|
||||
* Maximum depth of a tree (Node type). Every Node can have up to 5 children.
|
||||
*/
|
||||
|
||||
/** Maximum depth of a tree (Node type). Every Node can have up to 5 children. */
|
||||
private static final int maxTreeDepth = 4;
|
||||
/**
|
||||
* Size of the vector of trees (Node type).
|
||||
*/
|
||||
|
||||
/** Size of the vector of trees (Node type). */
|
||||
private static final int treeVectorSize = 500;
|
||||
|
||||
private Value module;
|
||||
private Value benchFunc;
|
||||
|
||||
@ -62,24 +58,24 @@ public class EqualsBenchmarks {
|
||||
public void initializeBenchmark(BenchmarkParams params) throws Exception {
|
||||
var random = new Random(42);
|
||||
|
||||
var ctx = Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.option(
|
||||
RuntimeOptions.LOG_LEVEL,
|
||||
Level.WARNING.getName()
|
||||
)
|
||||
.logHandler(System.err)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||
).build();
|
||||
var ctx =
|
||||
Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||
.logHandler(System.err)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath())
|
||||
.build();
|
||||
|
||||
var benchmarkName = SrcUtil.findName(params);
|
||||
var codeBuilder = new StringBuilder("""
|
||||
var codeBuilder =
|
||||
new StringBuilder(
|
||||
"""
|
||||
import Standard.Base.Data.Range.Extensions
|
||||
|
||||
|
||||
type Node
|
||||
C1 f1
|
||||
C2 f1 f2
|
||||
@ -88,14 +84,13 @@ public class EqualsBenchmarks {
|
||||
C5 f1 f2 f3 f4 f5
|
||||
Nil
|
||||
Value value
|
||||
|
||||
|
||||
eq_vec vec1 vec2 =
|
||||
(0.up_to vec1.length).map idx->
|
||||
(vec1.at idx) == (vec2.at idx)
|
||||
|
||||
|
||||
eq x y = x == y
|
||||
"""
|
||||
);
|
||||
""");
|
||||
// Indexes where `True` is expected. Inside the generated vectors, on a predefined indexes,
|
||||
// we put "constant" values, such that when the elements at these indexes are compared,
|
||||
// `True` is returned.
|
||||
@ -103,68 +98,53 @@ public class EqualsBenchmarks {
|
||||
|
||||
switch (benchmarkName) {
|
||||
case "equalsPrimitives" -> {
|
||||
trueExpectedAt = Set.of(
|
||||
primitiveVectorSize / 2,
|
||||
primitiveVectorSize / 4,
|
||||
primitiveVectorSize / 8,
|
||||
primitiveVectorSize / 16,
|
||||
primitiveVectorSize / 32,
|
||||
primitiveVectorSize / 64
|
||||
);
|
||||
trueExpectedAt =
|
||||
Set.of(
|
||||
primitiveVectorSize / 2,
|
||||
primitiveVectorSize / 4,
|
||||
primitiveVectorSize / 8,
|
||||
primitiveVectorSize / 16,
|
||||
primitiveVectorSize / 32,
|
||||
primitiveVectorSize / 64);
|
||||
codeBuilder
|
||||
.append(
|
||||
generateVectorOfPrimitives(primitiveVectorSize, "vec1", 42, trueExpectedAt, random)
|
||||
)
|
||||
generateVectorOfPrimitives(primitiveVectorSize, "vec1", 42, trueExpectedAt, random))
|
||||
.append("\n")
|
||||
.append(
|
||||
generateVectorOfPrimitives(primitiveVectorSize, "vec2", 42, trueExpectedAt, random)
|
||||
)
|
||||
generateVectorOfPrimitives(primitiveVectorSize, "vec2", 42, trueExpectedAt, random))
|
||||
.append("\n");
|
||||
}
|
||||
case "equalsStrings" -> {
|
||||
trueExpectedAt = Set.of(
|
||||
treeVectorSize / 2,
|
||||
treeVectorSize / 4,
|
||||
treeVectorSize / 8
|
||||
);
|
||||
trueExpectedAt = Set.of(treeVectorSize / 2, treeVectorSize / 4, treeVectorSize / 8);
|
||||
codeBuilder
|
||||
.append(
|
||||
generateVectorOfStrings(stringsVectorSize, "vec1", "AAA", trueExpectedAt, random)
|
||||
)
|
||||
generateVectorOfStrings(stringsVectorSize, "vec1", "AAA", trueExpectedAt, random))
|
||||
.append("\n")
|
||||
.append(
|
||||
generateVectorOfStrings(stringsVectorSize, "vec2", "AAA", trueExpectedAt, random)
|
||||
)
|
||||
generateVectorOfStrings(stringsVectorSize, "vec2", "AAA", trueExpectedAt, random))
|
||||
.append("\n");
|
||||
}
|
||||
case "equalsTrees" -> {
|
||||
trueExpectedAt = Set.of(
|
||||
treeVectorSize / 2,
|
||||
treeVectorSize / 4,
|
||||
treeVectorSize / 8,
|
||||
treeVectorSize / 16
|
||||
);
|
||||
trueExpectedAt =
|
||||
Set.of(treeVectorSize / 2, treeVectorSize / 4, treeVectorSize / 8, treeVectorSize / 16);
|
||||
codeBuilder
|
||||
.append(
|
||||
generateVectorOfTrees(treeVectorSize, "vec1", maxTreeDepth, createNilNode(), trueExpectedAt, random)
|
||||
)
|
||||
generateVectorOfTrees(
|
||||
treeVectorSize, "vec1", maxTreeDepth, createNilNode(), trueExpectedAt, random))
|
||||
.append("\n")
|
||||
.append(
|
||||
generateVectorOfTrees(treeVectorSize, "vec2", maxTreeDepth, createNilNode(), trueExpectedAt, random)
|
||||
)
|
||||
generateVectorOfTrees(
|
||||
treeVectorSize, "vec2", maxTreeDepth, createNilNode(), trueExpectedAt, random))
|
||||
.append("\n");
|
||||
}
|
||||
default ->
|
||||
throw new IllegalStateException("Unexpected benchmark: " + params.getBenchmark());
|
||||
default -> throw new IllegalStateException("Unexpected benchmark: " + params.getBenchmark());
|
||||
}
|
||||
|
||||
codeBuilder.append("""
|
||||
bench x = eq_vec vec1 vec2
|
||||
""");
|
||||
|
||||
module = ctx.eval(
|
||||
SrcUtil.source(benchmarkName, codeBuilder.toString())
|
||||
);
|
||||
module = ctx.eval(SrcUtil.source(benchmarkName, codeBuilder.toString()));
|
||||
|
||||
benchFunc = module.invokeMember(Module.EVAL_EXPRESSION, "bench");
|
||||
|
||||
@ -181,8 +161,9 @@ public class EqualsBenchmarks {
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over {@link #primitiveVectorSize} long vector of random generated primitive values - integers,
|
||||
* doubles, and strings
|
||||
* Iterates over {@link #primitiveVectorSize} long vector of random generated primitive values -
|
||||
* integers, doubles, and strings
|
||||
*
|
||||
* @param blackHole
|
||||
*/
|
||||
@Benchmark
|
||||
@ -207,21 +188,26 @@ public class EqualsBenchmarks {
|
||||
|
||||
/**
|
||||
* Generates source code for a vector of primitive values. The vector will contain integers and
|
||||
* doubles. Count of elements of these different value types is equally distributed,
|
||||
* i.e., there is exact same amount of integers and doubles. Vector is
|
||||
* shuffled, so that there should not be a long consecutive range of values of just one type.
|
||||
* <p>
|
||||
* Generates code of form {@code vecName = [...]}
|
||||
* doubles. Count of elements of these different value types is equally distributed, i.e., there
|
||||
* is exact same amount of integers and doubles. Vector is shuffled, so that there should not be a
|
||||
* long consecutive range of values of just one type.
|
||||
*
|
||||
* <p>Generates code of form {@code vecName = [...]}
|
||||
*
|
||||
* @param totalSize Total size of the generated vector.
|
||||
* @param vecName Name of the generated vector.
|
||||
* @param identityElem A primitive element considered an identity with respect to `==` operator,
|
||||
* will be put in indexes denoted by {@code constantIdxs}
|
||||
* will be put in indexes denoted by {@code constantIdxs}
|
||||
* @param constantIdxs Indexes where {@code identityElem} will be put.
|
||||
* @param random Random number generator.
|
||||
* @return Source of the generated vector
|
||||
*/
|
||||
private static String generateVectorOfPrimitives(int totalSize, String vecName, Object identityElem, Collection<Integer> constantIdxs, Random random) {
|
||||
private static String generateVectorOfPrimitives(
|
||||
int totalSize,
|
||||
String vecName,
|
||||
Object identityElem,
|
||||
Collection<Integer> constantIdxs,
|
||||
Random random) {
|
||||
var partSize = totalSize / 2;
|
||||
List<Object> primitiveValues = new ArrayList<>();
|
||||
random.ints(partSize).forEach(primitiveValues::add);
|
||||
@ -245,7 +231,12 @@ public class EqualsBenchmarks {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String generateVectorOfStrings(int size, String vecName, String identityElem, Collection<Integer> identityIdxs, Random random) {
|
||||
private static String generateVectorOfStrings(
|
||||
int size,
|
||||
String vecName,
|
||||
String identityElem,
|
||||
Collection<Integer> identityIdxs,
|
||||
Random random) {
|
||||
var sb = new StringBuilder();
|
||||
sb.append(vecName).append(" = [");
|
||||
for (int i = 0; i < size; i++) {
|
||||
@ -263,25 +254,29 @@ public class EqualsBenchmarks {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates source code for a vector of trees (Node type), i.e., generates an expression
|
||||
* {@code vecName = [...]}.
|
||||
* Generates source code for a vector of trees (Node type), i.e., generates an expression {@code
|
||||
* vecName = [...]}.
|
||||
*
|
||||
* @param size Total size of the generated vector.
|
||||
* @param vecName How the vector should be named.
|
||||
* @param maxDepth Maximum depth of the generated tree. Note that there is no lower bound, so
|
||||
* the generated tree can have depth 1.
|
||||
* @param identityNode A node that is considered an identity with respect to `==` operator.
|
||||
* This node will be put on every indes of {@code constantIdxs}.
|
||||
* @param maxDepth Maximum depth of the generated tree. Note that there is no lower bound, so the
|
||||
* generated tree can have depth 1.
|
||||
* @param identityNode A node that is considered an identity with respect to `==` operator. This
|
||||
* node will be put on every indes of {@code constantIdxs}.
|
||||
* @param constantIdxs Indexes in the vector where {@code identityNode} should be put.
|
||||
* @param random Random number generator.
|
||||
* @return Source code for the generated tree.
|
||||
*/
|
||||
private static String generateVectorOfTrees(int size, String vecName, int maxDepth, Node identityNode, Collection<Integer> constantIdxs, Random random) {
|
||||
private static String generateVectorOfTrees(
|
||||
int size,
|
||||
String vecName,
|
||||
int maxDepth,
|
||||
Node identityNode,
|
||||
Collection<Integer> constantIdxs,
|
||||
Random random) {
|
||||
var trees = new ArrayList<Node>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
trees.add(
|
||||
generateTree(null, 0, random, maxDepth)
|
||||
);
|
||||
trees.add(generateTree(null, 0, random, maxDepth));
|
||||
}
|
||||
for (Integer constantIdx : constantIdxs) {
|
||||
trees.set(constantIdx, identityNode);
|
||||
@ -302,11 +297,7 @@ public class EqualsBenchmarks {
|
||||
node = new NilNode(currDepth, parent);
|
||||
} else {
|
||||
if (random.nextBoolean() && currDepth > 0) {
|
||||
node = new ValueNode(
|
||||
currDepth,
|
||||
parent,
|
||||
random.nextInt()
|
||||
);
|
||||
node = new ValueNode(currDepth, parent, random.nextInt());
|
||||
} else {
|
||||
node = new NodeWithChildren(currDepth, parent);
|
||||
// childCount is between 1..5
|
||||
@ -318,26 +309,21 @@ public class EqualsBenchmarks {
|
||||
}
|
||||
if (parent instanceof NodeWithChildren parentWithChildren) {
|
||||
parentWithChildren.addChild(node);
|
||||
} else if (parent != null){
|
||||
} else if (parent != null) {
|
||||
throw new AssertionError("expected parent to be NodeWithChildren or null");
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private static final String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQESRTUVWXYZ";
|
||||
|
||||
private static String randomString(int size, Random random) {
|
||||
var sb = new StringBuilder(size);
|
||||
random
|
||||
.ints(size, 0, characters.length())
|
||||
.mapToObj(characters::charAt)
|
||||
.forEach(sb::append);
|
||||
random.ints(size, 0, characters.length()).mapToObj(characters::charAt).forEach(sb::append);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A simple hierarchy of Node classes that simplifies source code generation.
|
||||
*/
|
||||
/** A simple hierarchy of Node classes that simplifies source code generation. */
|
||||
abstract static class Node {
|
||||
final int depth;
|
||||
final Node parent;
|
||||
@ -352,6 +338,7 @@ public class EqualsBenchmarks {
|
||||
|
||||
private static final class ValueNode extends Node {
|
||||
final int value;
|
||||
|
||||
ValueNode(int depth, Node parent, int value) {
|
||||
super(depth, parent);
|
||||
this.value = value;
|
||||
@ -388,14 +375,16 @@ public class EqualsBenchmarks {
|
||||
|
||||
@Override
|
||||
public String createSource() {
|
||||
String ctor = switch(children.size()) {
|
||||
case 1 -> "(Node.C1 ";
|
||||
case 2 -> "(Node.C2 ";
|
||||
case 3 -> "(Node.C3 ";
|
||||
case 4 -> "(Node.C4 ";
|
||||
case 5 -> "(Node.C5 ";
|
||||
default -> throw new AssertionError("Unexpected number of children: " + children.size());
|
||||
};
|
||||
String ctor =
|
||||
switch (children.size()) {
|
||||
case 1 -> "(Node.C1 ";
|
||||
case 2 -> "(Node.C2 ";
|
||||
case 3 -> "(Node.C3 ";
|
||||
case 4 -> "(Node.C4 ";
|
||||
case 5 -> "(Node.C5 ";
|
||||
default -> throw new AssertionError(
|
||||
"Unexpected number of children: " + children.size());
|
||||
};
|
||||
var sb = new StringBuilder();
|
||||
sb.append(ctor);
|
||||
for (Node child : children) {
|
||||
|
@ -9,7 +9,6 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.enso.interpreter.test.TestBase;
|
||||
import org.enso.polyglot.MethodNames.Module;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
@ -49,26 +48,24 @@ public class IfVsCaseBenchmarks extends TestBase {
|
||||
@Setup
|
||||
public void initializeBench(BenchmarkParams params) throws IOException {
|
||||
OutputStream out = new ByteArrayOutputStream();
|
||||
ctx = Context.newBuilder("enso")
|
||||
.allowAllAccess(true)
|
||||
.option(
|
||||
RuntimeOptions.LOG_LEVEL,
|
||||
Level.WARNING.getName()
|
||||
)
|
||||
.logHandler(System.err)
|
||||
.out(out)
|
||||
.err(out)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowExperimentalOptions(true)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||
)
|
||||
.option("engine.MultiTier", "true")
|
||||
.option("engine.BackgroundCompilation", "true")
|
||||
.build();
|
||||
ctx =
|
||||
Context.newBuilder("enso")
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||
.logHandler(System.err)
|
||||
.out(out)
|
||||
.err(out)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowExperimentalOptions(true)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath())
|
||||
.option("engine.MultiTier", "true")
|
||||
.option("engine.BackgroundCompilation", "true")
|
||||
.build();
|
||||
|
||||
var code = """
|
||||
var code =
|
||||
"""
|
||||
from Standard.Base import all
|
||||
|
||||
type My_Type
|
||||
@ -142,10 +139,13 @@ public class IfVsCaseBenchmarks extends TestBase {
|
||||
var src = SrcUtil.source(benchmarkName, code);
|
||||
Value module = ctx.eval(src);
|
||||
ifBench3 = Objects.requireNonNull(module.invokeMember(Module.EVAL_EXPRESSION, "if_bench_3"));
|
||||
caseBench3 = Objects.requireNonNull(module.invokeMember(Module.EVAL_EXPRESSION, "case_bench_3"));
|
||||
caseBench3 =
|
||||
Objects.requireNonNull(module.invokeMember(Module.EVAL_EXPRESSION, "case_bench_3"));
|
||||
ifBench6 = Objects.requireNonNull(module.invokeMember(Module.EVAL_EXPRESSION, "if_bench_6"));
|
||||
ifBench6In = Objects.requireNonNull(module.invokeMember(Module.EVAL_EXPRESSION, "if_bench_6_in"));
|
||||
caseBench6 = Objects.requireNonNull(module.invokeMember(Module.EVAL_EXPRESSION, "case_bench_6"));
|
||||
ifBench6In =
|
||||
Objects.requireNonNull(module.invokeMember(Module.EVAL_EXPRESSION, "if_bench_6_in"));
|
||||
caseBench6 =
|
||||
Objects.requireNonNull(module.invokeMember(Module.EVAL_EXPRESSION, "case_bench_6"));
|
||||
createVec = Objects.requireNonNull(module.invokeMember(Module.EVAL_EXPRESSION, "create_vec"));
|
||||
// So far, input is a vector of My_Type.Value with all fields set to True
|
||||
inputVec = createMyTypeAllTrue(INPUT_VEC_SIZE);
|
||||
@ -156,9 +156,7 @@ public class IfVsCaseBenchmarks extends TestBase {
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates over a vector of {@code My_Type} values with True only fields.
|
||||
*/
|
||||
/** Iterates over a vector of {@code My_Type} values with True only fields. */
|
||||
@Benchmark
|
||||
public void ifBench3() {
|
||||
Value res = ifBench3.execute(inputVec);
|
||||
@ -195,9 +193,7 @@ public class IfVsCaseBenchmarks extends TestBase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a vector of {@code My_Type} with all True fields
|
||||
*/
|
||||
/** Creates a vector of {@code My_Type} with all True fields */
|
||||
private Value createMyTypeAllTrue(int size) {
|
||||
List<List<Boolean>> inputPolyVec = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
|
@ -1,11 +1,9 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
@ -23,7 +21,6 @@ import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.infra.BenchmarkParams;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@Warmup(iterations = 3)
|
||||
@ -41,22 +38,21 @@ public class ListBenchmarks {
|
||||
|
||||
@Setup
|
||||
public void initializeBenchmark(BenchmarkParams params) throws Exception {
|
||||
var ctx = Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(
|
||||
RuntimeOptions.LOG_LEVEL,
|
||||
Level.WARNING.getName()
|
||||
)
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||
).build();
|
||||
var ctx =
|
||||
Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath())
|
||||
.build();
|
||||
|
||||
var benchmarkName = SrcUtil.findName(params);
|
||||
var code = """
|
||||
var code =
|
||||
"""
|
||||
from Standard.Base.Any import Any
|
||||
from Standard.Base.Data.List.List import Cons, Nil
|
||||
from Standard.Base.Data.Text import Text
|
||||
@ -135,12 +131,12 @@ public class ListBenchmarks {
|
||||
var module = ctx.eval(SrcUtil.source(benchmarkName, code));
|
||||
|
||||
this.self = module.invokeMember("get_associated_type");
|
||||
Function<String,Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||
Function<String, Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||
|
||||
this.plusOne = getMethod.apply("plus_one");
|
||||
|
||||
switch (benchmarkName) {
|
||||
case "mapOverList" -> {
|
||||
case "mapOverList" -> {
|
||||
this.list = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
this.zero = 0;
|
||||
this.sum = getMethod.apply("sum");
|
||||
@ -149,7 +145,7 @@ public class ListBenchmarks {
|
||||
throw new AssertionError("Expecting a number " + this.oldSum);
|
||||
}
|
||||
}
|
||||
case "mapAnyOverList" -> {
|
||||
case "mapAnyOverList" -> {
|
||||
this.list = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
this.zero = 0;
|
||||
this.sum = getMethod.apply("sum_any");
|
||||
@ -158,7 +154,7 @@ public class ListBenchmarks {
|
||||
throw new AssertionError("Expecting a number " + this.oldSum);
|
||||
}
|
||||
}
|
||||
case "mapMultiOverList" -> {
|
||||
case "mapMultiOverList" -> {
|
||||
this.list = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
this.zero = 0;
|
||||
this.sum = getMethod.apply("sum_multi");
|
||||
@ -167,7 +163,7 @@ public class ListBenchmarks {
|
||||
throw new AssertionError("Expecting a number " + this.oldSum);
|
||||
}
|
||||
}
|
||||
case "mapIntegerOverList" -> {
|
||||
case "mapIntegerOverList" -> {
|
||||
this.list = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
this.zero = 0;
|
||||
this.sum = getMethod.apply("sum_int");
|
||||
@ -176,7 +172,7 @@ public class ListBenchmarks {
|
||||
throw new AssertionError("Expecting a number " + this.oldSum);
|
||||
}
|
||||
}
|
||||
case "mapVOverList" -> {
|
||||
case "mapVOverList" -> {
|
||||
this.list = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
this.zero = getMethod.apply("v_zero").execute(self);
|
||||
this.sum = getMethod.apply("v_sum_int");
|
||||
@ -185,7 +181,7 @@ public class ListBenchmarks {
|
||||
throw new AssertionError("Expecting a number " + this.oldSum);
|
||||
}
|
||||
}
|
||||
case "mapConvOverList" -> {
|
||||
case "mapConvOverList" -> {
|
||||
this.list = getMethod.apply("generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
this.zero = getMethod.apply("v_zero").execute(self);
|
||||
this.sum = getMethod.apply("v_sum_conv");
|
||||
@ -194,7 +190,7 @@ public class ListBenchmarks {
|
||||
throw new AssertionError("Expecting a number " + this.oldSum);
|
||||
}
|
||||
}
|
||||
case "mapOverLazyList" -> {
|
||||
case "mapOverLazyList" -> {
|
||||
this.list = getMethod.apply("lenivy_generator").execute(self, LENGTH_OF_EXPERIMENT);
|
||||
this.zero = 0;
|
||||
this.sum = getMethod.apply("leniva_suma");
|
||||
@ -253,4 +249,3 @@ public class ListBenchmarks {
|
||||
hole.consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,10 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.AbstractList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
@ -25,7 +22,6 @@ import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.infra.BenchmarkParams;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@Warmup(iterations = 3)
|
||||
@ -33,29 +29,28 @@ import org.openjdk.jmh.infra.Blackhole;
|
||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||
@State(Scope.Benchmark)
|
||||
public class NestedPatternCompilationBenchmarks {
|
||||
private Value self;
|
||||
private String benchmarkName;
|
||||
private String code;
|
||||
private Context ctx;
|
||||
private Value self;
|
||||
private String benchmarkName;
|
||||
private String code;
|
||||
private Context ctx;
|
||||
|
||||
@Setup
|
||||
public void initializeBenchmark(BenchmarkParams params) throws Exception {
|
||||
ctx = Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(
|
||||
RuntimeOptions.LOG_LEVEL,
|
||||
Level.WARNING.getName()
|
||||
)
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||
).build();
|
||||
@Setup
|
||||
public void initializeBenchmark(BenchmarkParams params) throws Exception {
|
||||
ctx =
|
||||
Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath())
|
||||
.build();
|
||||
|
||||
benchmarkName = SrcUtil.findName(params);
|
||||
code = """
|
||||
benchmarkName = SrcUtil.findName(params);
|
||||
code =
|
||||
"""
|
||||
type List
|
||||
Cons a b
|
||||
Nil
|
||||
@ -73,29 +68,27 @@ public class NestedPatternCompilationBenchmarks {
|
||||
list_of_6 =
|
||||
List.Cons 1 (List.Cons 2 (List.Cons 3 (List.Cons 4 (List.Cons 5 (List.Cons 6 List.Nil)))))
|
||||
""";
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void sumList(Blackhole hole) throws IOException {
|
||||
// Compilation is included in the benchmark on purpose
|
||||
var module = ctx.eval(SrcUtil.source(benchmarkName, code));
|
||||
|
||||
this.self = module.invokeMember("get_associated_type");
|
||||
Function<String, Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||
|
||||
var list = getMethod.apply("list_of_6").execute(self);
|
||||
var result = getMethod.apply("test").execute(self, list);
|
||||
|
||||
if (!result.fitsInDouble()) {
|
||||
throw new AssertionError("Shall be a double: " + result);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void sumList(Blackhole hole) throws IOException {
|
||||
// Compilation is included in the benchmark on purpose
|
||||
var module = ctx.eval(SrcUtil.source(benchmarkName, code));
|
||||
|
||||
this.self = module.invokeMember("get_associated_type");
|
||||
Function<String,Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||
|
||||
var list = getMethod.apply("list_of_6").execute(self);
|
||||
var result = getMethod.apply("test").execute(self, list);
|
||||
|
||||
if (!result.fitsInDouble()) {
|
||||
throw new AssertionError("Shall be a double: " + result);
|
||||
}
|
||||
var calculated = (long) result.asDouble();
|
||||
var expected = 21;
|
||||
if (calculated != expected) {
|
||||
throw new AssertionError("Expected " + expected + " from sum but got " + calculated);
|
||||
}
|
||||
hole.consume(result);
|
||||
var calculated = (long) result.asDouble();
|
||||
var expected = 21;
|
||||
if (calculated != expected) {
|
||||
throw new AssertionError("Expected " + expected + " from sum but got " + calculated);
|
||||
}
|
||||
|
||||
hole.consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.enso.interpreter.bench.fixtures.semantic.RecursionFixtures;
|
||||
import org.enso.interpreter.test.DefaultInterpreterRunner;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@Warmup(iterations = 5)
|
||||
|
@ -1,15 +1,15 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import org.graalvm.polyglot.Source;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import org.openjdk.jmh.infra.BenchmarkParams;
|
||||
|
||||
final class SrcUtil {
|
||||
private SrcUtil() {
|
||||
}
|
||||
private SrcUtil() {}
|
||||
|
||||
static String findName(BenchmarkParams params) {
|
||||
return params.getBenchmark().replaceFirst(".*\\.", "");
|
||||
@ -19,7 +19,7 @@ final class SrcUtil {
|
||||
var d = new File(new File(new File("."), "target"), "bench-data");
|
||||
d.mkdirs();
|
||||
var f = new File(d, benchmarkName + ".enso");
|
||||
try ( var w = new FileWriter(f)) {
|
||||
try (var w = new FileWriter(f)) {
|
||||
w.write(code);
|
||||
}
|
||||
return Source.newBuilder("enso", f).build();
|
||||
|
@ -1,11 +1,9 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
@ -23,7 +21,6 @@ import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.infra.BenchmarkParams;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@Warmup(iterations = 3)
|
||||
@ -37,21 +34,20 @@ public class StringBenchmarks {
|
||||
|
||||
@Setup
|
||||
public void initializeBenchmark(BenchmarkParams params) throws Exception {
|
||||
var ctx = Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(
|
||||
RuntimeOptions.LOG_LEVEL,
|
||||
Level.WARNING.getName()
|
||||
)
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||
).build();
|
||||
var ctx =
|
||||
Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath())
|
||||
.build();
|
||||
|
||||
var code ="""
|
||||
var code =
|
||||
"""
|
||||
from Standard.Base import all
|
||||
|
||||
all_length v = v.fold 0 (sum -> str -> sum + str.length)
|
||||
@ -66,7 +62,7 @@ public class StringBenchmarks {
|
||||
var module = ctx.eval(src);
|
||||
|
||||
this.self = module.invokeMember("get_associated_type");
|
||||
Function<String,Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||
Function<String, Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||
|
||||
var repeat = 2000;
|
||||
var length = 1000;
|
||||
@ -88,4 +84,3 @@ public class StringBenchmarks {
|
||||
matter.consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,9 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import org.enso.polyglot.MethodNames.Module;
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
@ -9,13 +13,6 @@ import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.BenchmarkParams;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@Warmup(iterations = 3)
|
||||
@ -29,20 +26,19 @@ public class TypePatternBenchmarks {
|
||||
|
||||
@Setup
|
||||
public void initializeBenchmark(BenchmarkParams params) throws Exception {
|
||||
var ctx = Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(
|
||||
RuntimeOptions.LOG_LEVEL,
|
||||
Level.WARNING.getName()
|
||||
)
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||
).build();
|
||||
var code ="""
|
||||
var ctx =
|
||||
Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath())
|
||||
.build();
|
||||
var code =
|
||||
"""
|
||||
from Standard.Base import Integer, Vector, Any, Float
|
||||
|
||||
avg arr =
|
||||
@ -72,7 +68,7 @@ public class TypePatternBenchmarks {
|
||||
var src = SrcUtil.source(benchmarkName, code);
|
||||
var module = ctx.eval(src);
|
||||
|
||||
Function<String,Value> getMethod = (name) -> module.invokeMember(Module.EVAL_EXPRESSION, name);
|
||||
Function<String, Value> getMethod = (name) -> module.invokeMember(Module.EVAL_EXPRESSION, name);
|
||||
|
||||
var length = 100;
|
||||
this.vec = getMethod.apply("gen_vec").execute(length, 1.1);
|
||||
@ -85,8 +81,9 @@ public class TypePatternBenchmarks {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adding @ExplodeLoop in {@link org.enso.interpreter.node.controlflow.caseexpr.CatchTypeBranchNode} specialization
|
||||
* decreases the performance of this benchmark.
|
||||
* Adding @ExplodeLoop in {@link
|
||||
* org.enso.interpreter.node.controlflow.caseexpr.CatchTypeBranchNode} specialization decreases
|
||||
* the performance of this benchmark.
|
||||
*/
|
||||
@Benchmark
|
||||
public void matchOverAny(Blackhole matter) {
|
||||
@ -94,7 +91,8 @@ public class TypePatternBenchmarks {
|
||||
}
|
||||
|
||||
/**
|
||||
* Benchmark that matches over a Float. The old (decimal) name is kept to keep the history of results consistent.
|
||||
* Benchmark that matches over a Float. The old (decimal) name is kept to keep the history of
|
||||
* results consistent.
|
||||
*/
|
||||
@Benchmark
|
||||
public void matchOverDecimal(Blackhole matter) {
|
||||
@ -113,4 +111,3 @@ public class TypePatternBenchmarks {
|
||||
matter.consume(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,10 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.AbstractList;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.enso.polyglot.RuntimeOptions;
|
||||
import org.graalvm.polyglot.Context;
|
||||
import org.graalvm.polyglot.Value;
|
||||
@ -24,7 +22,6 @@ import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.infra.BenchmarkParams;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@Warmup(iterations = 3)
|
||||
@ -38,22 +35,21 @@ public class VectorBenchmarks {
|
||||
|
||||
@Setup
|
||||
public void initializeBenchmark(BenchmarkParams params) throws Exception {
|
||||
var ctx = Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(
|
||||
RuntimeOptions.LOG_LEVEL,
|
||||
Level.WARNING.getName()
|
||||
)
|
||||
var ctx =
|
||||
Context.newBuilder()
|
||||
.allowExperimentalOptions(true)
|
||||
.allowIO(IOAccess.ALL)
|
||||
.allowAllAccess(true)
|
||||
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
|
||||
.logHandler(System.err)
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath()
|
||||
).build();
|
||||
.option(
|
||||
"enso.languageHomeOverride",
|
||||
Paths.get("../../distribution/component").toFile().getAbsolutePath())
|
||||
.build();
|
||||
|
||||
var benchmarkName = SrcUtil.findName(params);
|
||||
var code = """
|
||||
var code =
|
||||
"""
|
||||
import Standard.Base.Data.Vector.Vector
|
||||
import Standard.Base.Data.Array_Proxy.Array_Proxy
|
||||
|
||||
@ -89,49 +85,57 @@ public class VectorBenchmarks {
|
||||
var module = ctx.eval(SrcUtil.source(benchmarkName, code));
|
||||
|
||||
this.self = module.invokeMember("get_associated_type");
|
||||
Function<String,Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||
Function<String, Value> getMethod = (name) -> module.invokeMember("get_method", self, name);
|
||||
|
||||
var length = 1000;
|
||||
Value vec = getMethod.apply("fibarr").execute(self, length, Integer.MAX_VALUE);
|
||||
|
||||
switch (benchmarkName) {
|
||||
case "averageOverVector": {
|
||||
this.arrayOfFibNumbers = vec;
|
||||
break;
|
||||
}
|
||||
case "averageOverSlice": {
|
||||
this.arrayOfFibNumbers = getMethod.apply("slice").execute(self, vec, 1, length);
|
||||
break;
|
||||
}
|
||||
case "averageOverArray": {
|
||||
this.arrayOfFibNumbers = getMethod.apply("to_array").execute(self, vec);
|
||||
break;
|
||||
}
|
||||
case "averageOverPolyglotVector": {
|
||||
long[] copy = copyToPolyglotArray(vec);
|
||||
this.arrayOfFibNumbers = getMethod.apply("to_vector").execute(self, copy);
|
||||
break;
|
||||
}
|
||||
case "averageOverPolyglotArray": {
|
||||
long[] copy = copyToPolyglotArray(vec);
|
||||
this.arrayOfFibNumbers = Value.asValue(copy);
|
||||
break;
|
||||
}
|
||||
case "averageOverArrayProxy": {
|
||||
this.arrayOfFibNumbers = getMethod.apply("create_array_proxy").execute(self, vec);
|
||||
break;
|
||||
}
|
||||
case "averageOverArrayProxyNew": {
|
||||
this.arrayOfFibNumbers = getMethod.apply("create_array_proxy_new").execute(self, vec);
|
||||
break;
|
||||
}
|
||||
case "averageAbstractList": {
|
||||
long[] copy = copyToPolyglotArray(vec);
|
||||
final ProxyList<Long> proxyList = new ProxyList<Long>();
|
||||
getMethod.apply("fill_proxy").execute(self, proxyList, copy);
|
||||
this.arrayOfFibNumbers = Value.asValue(proxyList);
|
||||
break;
|
||||
}
|
||||
case "averageOverVector":
|
||||
{
|
||||
this.arrayOfFibNumbers = vec;
|
||||
break;
|
||||
}
|
||||
case "averageOverSlice":
|
||||
{
|
||||
this.arrayOfFibNumbers = getMethod.apply("slice").execute(self, vec, 1, length);
|
||||
break;
|
||||
}
|
||||
case "averageOverArray":
|
||||
{
|
||||
this.arrayOfFibNumbers = getMethod.apply("to_array").execute(self, vec);
|
||||
break;
|
||||
}
|
||||
case "averageOverPolyglotVector":
|
||||
{
|
||||
long[] copy = copyToPolyglotArray(vec);
|
||||
this.arrayOfFibNumbers = getMethod.apply("to_vector").execute(self, copy);
|
||||
break;
|
||||
}
|
||||
case "averageOverPolyglotArray":
|
||||
{
|
||||
long[] copy = copyToPolyglotArray(vec);
|
||||
this.arrayOfFibNumbers = Value.asValue(copy);
|
||||
break;
|
||||
}
|
||||
case "averageOverArrayProxy":
|
||||
{
|
||||
this.arrayOfFibNumbers = getMethod.apply("create_array_proxy").execute(self, vec);
|
||||
break;
|
||||
}
|
||||
case "averageOverArrayProxyNew":
|
||||
{
|
||||
this.arrayOfFibNumbers = getMethod.apply("create_array_proxy_new").execute(self, vec);
|
||||
break;
|
||||
}
|
||||
case "averageAbstractList":
|
||||
{
|
||||
long[] copy = copyToPolyglotArray(vec);
|
||||
final ProxyList<Long> proxyList = new ProxyList<Long>();
|
||||
getMethod.apply("fill_proxy").execute(self, proxyList, copy);
|
||||
this.arrayOfFibNumbers = Value.asValue(proxyList);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new IllegalStateException("Unexpected benchmark: " + params.getBenchmark());
|
||||
@ -193,9 +197,12 @@ public class VectorBenchmarks {
|
||||
throw new AssertionError("Shall be a double: " + average);
|
||||
}
|
||||
var result = (long) average.asDouble();
|
||||
boolean isResultCorrect = (result >= 1019950590 && result <= 1019950600) || (result >= 1020971561 && result <= 1020971571);
|
||||
boolean isResultCorrect =
|
||||
(result >= 1019950590 && result <= 1019950600)
|
||||
|| (result >= 1020971561 && result <= 1020971571);
|
||||
if (!isResultCorrect) {
|
||||
throw new AssertionError("Expecting reasonable average but was " + result + "\n" + arrayOfFibNumbers);
|
||||
throw new AssertionError(
|
||||
"Expecting reasonable average but was " + result + "\n" + arrayOfFibNumbers);
|
||||
}
|
||||
hole.consume(result);
|
||||
}
|
||||
@ -220,4 +227,3 @@ public class VectorBenchmarks {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,11 @@
|
||||
package org.enso.interpreter.bench.benchmarks.semantic;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.enso.interpreter.test.TestBase;
|
||||
import org.enso.polyglot.MethodNames;
|
||||
import org.graalvm.polyglot.Context;
|
||||
@ -10,20 +16,13 @@ import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.Setup;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.TearDown;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
import org.openjdk.jmh.infra.BenchmarkParams;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Fork(1)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@ -31,49 +30,52 @@ import java.util.concurrent.TimeUnit;
|
||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||
@State(Scope.Benchmark)
|
||||
public class WarningBenchmarks extends TestBase {
|
||||
private static final int INPUT_VEC_SIZE = 10_000;
|
||||
private static final int INPUT_DIFF_VEC_SIZE = 10_000;
|
||||
private Context ctx;
|
||||
private Value vecSumBench;
|
||||
private static final int INPUT_VEC_SIZE = 10_000;
|
||||
private static final int INPUT_DIFF_VEC_SIZE = 10_000;
|
||||
private Context ctx;
|
||||
private Value vecSumBench;
|
||||
|
||||
private Value createVec;
|
||||
private Value mapVecWithWarnings;
|
||||
private Value noWarningsVec;
|
||||
private Value sameWarningVec;
|
||||
private Value randomVec;
|
||||
private Value randomElemsWithWarningsVec;
|
||||
private Value constElem;
|
||||
private Value constElemWithWarning;
|
||||
private Value createVec;
|
||||
private Value mapVecWithWarnings;
|
||||
private Value noWarningsVec;
|
||||
private Value sameWarningVec;
|
||||
private Value randomVec;
|
||||
private Value randomElemsWithWarningsVec;
|
||||
private Value constElem;
|
||||
private Value constElemWithWarning;
|
||||
|
||||
private String benchmarkName;
|
||||
private String benchmarkName;
|
||||
|
||||
private int randomVectorSum = 0;
|
||||
private int randomVectorSum = 0;
|
||||
|
||||
private record GeneratedVector(StringBuilder repr, int sum) {}
|
||||
private record GeneratedVector(StringBuilder repr, int sum) {}
|
||||
|
||||
private GeneratedVector generateRandomVector(Random random, String vectorName, long vectorSize, int maxRange) {
|
||||
List<Integer> primitiveValues = new ArrayList<>();
|
||||
random.ints(vectorSize, 0, maxRange).forEach(primitiveValues::add);
|
||||
var sb = new StringBuilder();
|
||||
sb.append(vectorName).append(" = [");
|
||||
var sum = 0;
|
||||
for (Integer intValue : primitiveValues) {
|
||||
sb.append(intValue).append(",");
|
||||
sum += Math.abs(intValue);
|
||||
}
|
||||
sb.setCharAt(sb.length() - 1, ']');
|
||||
sb.append('\n');
|
||||
return new GeneratedVector(sb, sum);
|
||||
private GeneratedVector generateRandomVector(
|
||||
Random random, String vectorName, long vectorSize, int maxRange) {
|
||||
List<Integer> primitiveValues = new ArrayList<>();
|
||||
random.ints(vectorSize, 0, maxRange).forEach(primitiveValues::add);
|
||||
var sb = new StringBuilder();
|
||||
sb.append(vectorName).append(" = [");
|
||||
var sum = 0;
|
||||
for (Integer intValue : primitiveValues) {
|
||||
sb.append(intValue).append(",");
|
||||
sum += Math.abs(intValue);
|
||||
}
|
||||
sb.setCharAt(sb.length() - 1, ']');
|
||||
sb.append('\n');
|
||||
return new GeneratedVector(sb, sum);
|
||||
}
|
||||
|
||||
@Setup
|
||||
public void initializeBench(BenchmarkParams params) throws IOException {
|
||||
ctx = createDefaultContext();
|
||||
var random = new Random(42);
|
||||
@Setup
|
||||
public void initializeBench(BenchmarkParams params) throws IOException {
|
||||
ctx = createDefaultContext();
|
||||
var random = new Random(42);
|
||||
|
||||
benchmarkName = SrcUtil.findName(params);
|
||||
benchmarkName = SrcUtil.findName(params);
|
||||
|
||||
var code = new StringBuilder("""
|
||||
var code =
|
||||
new StringBuilder(
|
||||
"""
|
||||
from Standard.Base import all
|
||||
|
||||
vec_sum_bench : Vector Integer -> Integer
|
||||
@ -82,10 +84,10 @@ public class WarningBenchmarks extends TestBase {
|
||||
|
||||
create_vec size elem =
|
||||
Vector.fill size elem
|
||||
|
||||
|
||||
elem =
|
||||
42
|
||||
|
||||
|
||||
elem_const_with_warning =
|
||||
x = 42
|
||||
Warning.attach "Foo!" x
|
||||
@ -97,59 +99,69 @@ public class WarningBenchmarks extends TestBase {
|
||||
vec.map (e-> elem_with_warning e)
|
||||
""");
|
||||
|
||||
// generate random vector
|
||||
var randomIntVectorName = "vector_with_random_values";
|
||||
var vectorWithRandomValues = generateRandomVector(random, randomIntVectorName, INPUT_DIFF_VEC_SIZE, 3_000);
|
||||
code.append(vectorWithRandomValues.repr());
|
||||
randomVectorSum = vectorWithRandomValues.sum();
|
||||
// generate random vector
|
||||
var randomIntVectorName = "vector_with_random_values";
|
||||
var vectorWithRandomValues =
|
||||
generateRandomVector(random, randomIntVectorName, INPUT_DIFF_VEC_SIZE, 3_000);
|
||||
code.append(vectorWithRandomValues.repr());
|
||||
randomVectorSum = vectorWithRandomValues.sum();
|
||||
|
||||
var src = SrcUtil.source(benchmarkName, code.toString());
|
||||
Value module = ctx.eval(src);
|
||||
vecSumBench = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "vec_sum_bench"));
|
||||
createVec = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create_vec"));
|
||||
mapVecWithWarnings = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "map_vector_with_warnings"));
|
||||
constElem = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "elem"));
|
||||
constElemWithWarning = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "elem_const_with_warning"));
|
||||
noWarningsVec = createVec.execute(INPUT_VEC_SIZE, constElem);
|
||||
sameWarningVec = createVec.execute(INPUT_VEC_SIZE, constElemWithWarning);
|
||||
randomVec = Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, randomIntVectorName));
|
||||
randomElemsWithWarningsVec = mapVecWithWarnings.execute(randomVec);
|
||||
var src = SrcUtil.source(benchmarkName, code.toString());
|
||||
Value module = ctx.eval(src);
|
||||
vecSumBench =
|
||||
Objects.requireNonNull(
|
||||
module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "vec_sum_bench"));
|
||||
createVec =
|
||||
Objects.requireNonNull(
|
||||
module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "create_vec"));
|
||||
mapVecWithWarnings =
|
||||
Objects.requireNonNull(
|
||||
module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "map_vector_with_warnings"));
|
||||
constElem =
|
||||
Objects.requireNonNull(module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "elem"));
|
||||
constElemWithWarning =
|
||||
Objects.requireNonNull(
|
||||
module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "elem_const_with_warning"));
|
||||
noWarningsVec = createVec.execute(INPUT_VEC_SIZE, constElem);
|
||||
sameWarningVec = createVec.execute(INPUT_VEC_SIZE, constElemWithWarning);
|
||||
randomVec =
|
||||
Objects.requireNonNull(
|
||||
module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, randomIntVectorName));
|
||||
randomElemsWithWarningsVec = mapVecWithWarnings.execute(randomVec);
|
||||
}
|
||||
|
||||
@TearDown
|
||||
public void cleanup() {
|
||||
ctx.close(true);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void noWarningsVecSum() {
|
||||
Value res = vecSumBench.execute(noWarningsVec);
|
||||
checkResult(res, INPUT_VEC_SIZE * 42);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void sameWarningVecSum() {
|
||||
Value res = vecSumBench.execute(sameWarningVec);
|
||||
checkResult(res, INPUT_VEC_SIZE * 42);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void randomElementsVecSum() {
|
||||
Value res = vecSumBench.execute(randomVec);
|
||||
checkResult(res, randomVectorSum);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void diffWarningRandomElementsVecSum() {
|
||||
Value res = vecSumBench.execute(randomElemsWithWarningsVec);
|
||||
checkResult(res, randomVectorSum);
|
||||
}
|
||||
|
||||
private static void checkResult(Value res, int expected) {
|
||||
if (res.asInt() != expected) {
|
||||
throw new AssertionError("Expected result: " + INPUT_VEC_SIZE * 42 + ", got: " + res.asInt());
|
||||
}
|
||||
|
||||
@TearDown
|
||||
public void cleanup() {
|
||||
ctx.close(true);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void noWarningsVecSum() {
|
||||
Value res = vecSumBench.execute(noWarningsVec);
|
||||
checkResult(res, INPUT_VEC_SIZE*42);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void sameWarningVecSum() {
|
||||
Value res = vecSumBench.execute(sameWarningVec);
|
||||
checkResult(res, INPUT_VEC_SIZE*42);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void randomElementsVecSum() {
|
||||
Value res = vecSumBench.execute(randomVec);
|
||||
checkResult(res, randomVectorSum);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void diffWarningRandomElementsVecSum() {
|
||||
Value res = vecSumBench.execute(randomElemsWithWarningsVec);
|
||||
checkResult(res, randomVectorSum);
|
||||
}
|
||||
|
||||
|
||||
private static void checkResult(Value res, int expected) {
|
||||
if (res.asInt() != expected) {
|
||||
throw new AssertionError("Expected result: " + INPUT_VEC_SIZE*42 + ", got: " + res.asInt());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user