Skip to content

Commit

Permalink
Simplify AnnotationsMappingBenchmark
Browse files Browse the repository at this point in the history
  • Loading branch information
mbovel committed May 7, 2024
1 parent 23cbeae commit a6f4514
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 173 deletions.
47 changes: 47 additions & 0 deletions bench-micro/scripts/compare.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/env bash

name=$1
commit_1=$2
commit_2=$3
runs=2

if [ -z "$name" ] || [ -z "$commit_1" ] || [ -z "$commit_2" ]; then
echo "Usage: $0 <benchmark-name> <commit-1> <commit-2>"
exit 1
fi

run_benchmark() {
jvm=$1
jvm_coursier_id=$2
run=$3
hash=$4
json_file=../../results/$hash-$name-$jvm-$run.json
txt_file=../../results/$hash-$name-$jvm-$run.txt
echo "Running $name benchmark with JVM $jvm at commit $hash, run $run out of $runs"
git checkout $hash
git clean -fdx
eval "$(coursier java --jvm "$jvm_coursier_id" --env)"
rm -rf "$json_file" "$stdout_file" .bloop .sbt .bsp .metals target
sbt "clean; scala3-bench-micro / Jmh / run -rf JSON -rff $json_file -o $txt_file $name"
}

read -p "WARNING: This script will brutally reset your Dotty repo (git clean -fdx). Type 'y' to continue." -n 1 -r
echo # new line

read -p "This script should be run form the Dotty root repository. Type 'y' to continue." -n 1 -r
echo # new line

read -p "This script will create an empty directory <dotty>/../results to store results. Type 'y' to continue." -n 1 -r
echo # new line

mkdir ../results

if [[ $REPLY =~ ^[Yy]$ ]]
then
for run in $(seq 1 $runs); do
for hash in $commit_1 $commit_2; do
run_benchmark openjdk "adoptium:1.17.0.11" $run $hash
run_benchmark graal "graalvm-java17:22.3.3" $run $hash
done
done
fi
21 changes: 0 additions & 21 deletions bench-micro/scripts/run.sh

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ import dotty.tools.dotc.typer.TyperPhase
*
* Run with: scala3-bench-micro / Jmh / run AnnotationsMappingBenchmark
*/
@Fork(value = 5)
// Set to 0 to record all iterations. We remove the first iterations manually
// when processing the results.
@Warmup(iterations = 0, time = 1, timeUnit = SECONDS)
@Measurement(iterations = 10, time = 1, timeUnit = SECONDS)
@Fork(value = 4)
@Warmup(iterations = 4, time = 1, timeUnit = SECONDS)
@Measurement(iterations = 4, time = 1, timeUnit = SECONDS)
@BenchmarkMode(Array(JMHMode.Throughput))
@State(Scope.Thread)
class AnnotationsMappingBenchmark:
Expand All @@ -38,17 +36,11 @@ class AnnotationsMappingBenchmark:
@Param(Array("v1", "v2", "v3", "v4"))
var valName: String = null

@Param(Array("current", "oldCheck", "newCheckEquals", "newCheckEq", "noCheck", "noCheckCopySymbols"))
var typeMapName: String = null

@Param(Array("id", "mapInts"))
var typeFunctionName: String = null

@Setup(Level.Iteration)
def setup(): Unit =
/** A custom phase that is used to retrieve the `Type`s and `Context` to be
* used in the benchmark.
*/
val testPhase =
new Phase:
final override def phaseName = "testPhase"
Expand All @@ -58,20 +50,15 @@ class AnnotationsMappingBenchmark:
specialIntTp = pkg.requiredClass("Test").requiredType("SpecialInt").typeRef
context = ctx

/** A custom compiler that only runs the `Parser`, `TyperPhase` and
* `testPhase`.
*/
val compiler =
new Compiler:
private final val baseCompiler = new Compiler()
final override def phases = List(List(Parser()), List(TyperPhase()), List(testPhase))

/** A custom driver that uses `compiler`. */
val driver =
new Driver:
final override def newCompiler(using Context): Compiler = compiler

// Runs the driver with the test file.
driver.process(Array("-classpath", System.getProperty("BENCH_CLASS_PATH"), "tests/someAnnotatedTypes.scala"))

typeFunction =
Expand All @@ -80,142 +67,8 @@ class AnnotationsMappingBenchmark:
case "mapInts" => tp => (if tp frozen_=:= defn.IntType then specialIntTp else tp)
case _ => throw new IllegalArgumentException(s"Unknown type function: $typeFunctionName")

/** Creates a new `TypeMap` that uses `mapConcreteAnnotationWith` to map
* concrete annotations. It is used to compare several ways to map these
* annotations.
*/
def makeTypeMap(mapConcreteAnnotationWith: (ConcreteAnnotation, TypeMap) => Context ?=> Annotation) =
typeMap =
new TypeMap(using context):
final override def apply(tp: Type): Type = typeFunction(mapOver(tp))
final override def mapOver(tp: Type) =
tp match
case tp @ AnnotatedType(underlying, annot) =>
val underlying1 = this(underlying)
val annot1 =
annot match
case annot: ConcreteAnnotation => mapConcreteAnnotationWith(annot, this)
case _ => annot.mapWith(this)
if annot1 eq EmptyAnnotation then underlying1
else derivedAnnotatedType(tp, underlying1, annot1)
case _ => super.mapOver(tp)

/** Retrieves all argument from a tree. This old implementation does not
* include type arguments.
*/
def oldAllArguments(tree: Tree)(using Context): List[Tree] =
tpd.unsplice(tree) match
case Apply(fn, args) => oldAllArguments(fn) ::: args
case TypeApply(fn, _) => oldAllArguments(fn)
case Block(_, expr) => oldAllArguments(expr)
case _ => Nil

/** This is the old (<= d1489734b7) implementation of `Annotation.mapWith`.
* It 1. does not include type arguments and 2. uses `frozen_=:=` to
* compare types and 3. does not copy all symbols.
*/
def oldMapWith(annot: ConcreteAnnotation, tm: TypeMap)(using Context): Annotation =
val tree = annot.tree
val args = oldAllArguments(tree)
if args.isEmpty then annot
else
val findDiff = new TreeAccumulator[Type]:
def apply(x: Type, tree: Tree)(using Context): Type =
if tm.isRange(x) then x
else
val tp1 = tm(tree.tpe)
foldOver(if tp1 frozen_=:= tree.tpe then x else tp1, tree)
val diff = findDiff(NoType, args)
if tm.isRange(diff) then EmptyAnnotation
else if diff.exists then annot.derivedAnnotation(tm.mapOver(tree))
else annot

/** Retrieves all argument from a tree, including type arguments. */
def newAllArguments(tree: Tree)(using Context): List[Tree] =
tpd.unsplice(tree) match
case Apply(fn, args) => newAllArguments(fn) ::: args
case TypeApply(fn, args) => newAllArguments(fn) ::: args
case Block(_, expr) => newAllArguments(expr)
case _ => Nil

/** This is the new implementation of `Annotation.mapWith`. It 1. includes
* type arguments and 2. uses `==` to compare types and 3. copies all
* symbols by using a custom `TreeTypeMap` that overrides `withMappedSyms`.
*/
def newMapWithEquals(annot: ConcreteAnnotation, tm: TypeMap)(using Context): Annotation =
val tree = annot.tree
val args = newAllArguments(tree)
if args.isEmpty then annot
else
val findDiff = new TreeAccumulator[Type]:
def apply(x: Type, tree: Tree)(using Context): Type =
if tm.isRange(x) then x
else
val tp1 = tm(tree.tpe)
foldOver(if tp1 == tree.tpe then x else tp1, tree)
val diff = findDiff(NoType, args)
if tm.isRange(diff) then EmptyAnnotation
else if diff.exists then
val ttm =
new TreeTypeMap(tm):
final override def withMappedSyms(syms: List[Symbol]): TreeTypeMap =
withMappedSyms(syms, mapSymbols(syms, this, mapAlways = true))
annot.derivedAnnotation(ttm.transform(tree))
else annot

/** Exactly the same as `newMapWithEquals`, but uses `eq` instead of `==` to
* compare types.
*/
def newMapWithEq(annot: ConcreteAnnotation, tm: TypeMap)(using Context): Annotation =
val tree = annot.tree
val args = newAllArguments(tree)
if args.isEmpty then annot
else
val findDiff = new TreeAccumulator[Type]:
def apply(x: Type, tree: Tree)(using Context): Type =
if tm.isRange(x) then x
else
val tp1 = tm(tree.tpe)
foldOver(if tp1 eq tree.tpe then x else tp1, tree)
val diff = findDiff(NoType, args)
if tm.isRange(diff) then EmptyAnnotation
else if diff.exists then
val ttm =
new TreeTypeMap(tm):
final override def withMappedSyms(syms: List[Symbol]): TreeTypeMap =
withMappedSyms(syms, mapSymbols(syms, this, mapAlways = true))
annot.derivedAnnotation(ttm.transform(tree))
else annot

def noCheckMapWith(annot: ConcreteAnnotation, tm: TypeMap)(using Context): Annotation =
annot.derivedAnnotation(tm.mapOver(annot.tree))

def noCheckCopySymbolsMapWith(annot: ConcreteAnnotation, tm: TypeMap)(using Context): Annotation =
val ttm =
new TreeTypeMap(tm):
final override def withMappedSyms(syms: List[Symbol]): TreeTypeMap =
withMappedSyms(syms, mapSymbols(syms, this, mapAlways = true))
annot.derivedAnnotation(ttm.transform(annot.tree))

typeMap =
typeMapName match
case "current" =>
new TypeMap(using context):
final override def apply(tp: Type): Type = typeFunction(mapOver(tp))
case "oldCheck" =>
makeTypeMap(oldMapWith)
case "newCheckEquals" =>
// This should be the same as `current`, modulo a few indirections.
makeTypeMap(newMapWithEq)
case "newCheckEq" =>
makeTypeMap(newMapWithEq)
case "noCheck" =>
makeTypeMap(noCheckMapWith)
case "noCheckCopySymbols" =>
makeTypeMap(noCheckCopySymbolsMapWith)
case _ =>
throw new IllegalArgumentException(s"Unknown type map: $typeMapName")

@Benchmark
def applyTypeMap() =
val res = typeMap.apply(tp)
// println(res.show(using context))
@Benchmark def applyTypeMap() = typeMap.apply(tp)

0 comments on commit a6f4514

Please sign in to comment.