Skip to content

Commit

Permalink
Parser micro-optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
kyri-petrou committed May 23, 2024
1 parent 32bb7ac commit c86d2aa
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 131 deletions.
183 changes: 90 additions & 93 deletions benchmarks/src/main/scala/caliban/ComplexQueryBenchmark.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,97 +58,94 @@ class ComplexQueryBenchmark {
}

object ComplexQueryBenchmark {
val fullIntrospectionQuery = """
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description
locations
args {
...InputValue
}
}
}
}
fragment FullType on __Type {
kind
name
description
fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}
fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
defaultValue
}
fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
"""
val fullIntrospectionQuery =
"""query IntrospectionQuery {
| __schema {
| queryType { name }
| mutationType { name }
| subscriptionType { name }
| types {
| ...FullType
| }
| directives {
| name
| description
| locations
| args {
| ...InputValue
| }
| }
| }
|}
|fragment FullType on __Type {
| kind
| name
| description
| fields(includeDeprecated: true) {
| name
| description
| args {
| ...InputValue
| }
| type {
| ...TypeRef
| }
| isDeprecated
| deprecationReason
| }
| inputFields {
| ...InputValue
| }
| interfaces {
| ...TypeRef
| }
| enumValues(includeDeprecated: true) {
| name
| description
| isDeprecated
| deprecationReason
| }
| possibleTypes {
| ...TypeRef
| }
|}
|fragment InputValue on __InputValue {
| name
| description
| type { ...TypeRef }
| defaultValue
|}
|fragment TypeRef on __Type {
| kind
| name
| ofType {
| kind
| name
| ofType {
| kind
| name
| ofType {
| kind
| name
| ofType {
| kind
| name
| ofType {
| kind
| name
| ofType {
| kind
| name
| ofType {
| kind
| name
| }
| }
| }
| }
| }
| }
| }
|}
|""".stripMargin
}
30 changes: 12 additions & 18 deletions benchmarks/src/main/scala/caliban/ParserBenchmark.scala
Original file line number Diff line number Diff line change
@@ -1,51 +1,45 @@
package caliban

import caliban.parsing.Parser
import cats.effect.IO
import io.circe.Json
import org.openjdk.jmh.annotations._
import sangria.execution._
import sangria.marshalling.circe._
import org.openjdk.jmh.infra.Blackhole
import sangria.parser.QueryParser

import java.util.concurrent.TimeUnit
import scala.concurrent.duration._
import scala.concurrent.{ Await, ExecutionContextExecutor, Future }
import scala.concurrent.ExecutionContextExecutor

@State(Scope.Thread)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
class ParserBenchmark {
import ComplexQueryBenchmark._

implicit val executionContext: ExecutionContextExecutor = scala.concurrent.ExecutionContext.global

@Benchmark
def runCaliban(): Unit = {
val io = Parser.parseQuery(fullIntrospectionQuery)
Caliban.run(io)
def runCaliban(bh: Blackhole): Unit = {
bh.consume(Parser.parseQueryEither(fullIntrospectionQuery).fold(throw _, identity))
()
}

@Benchmark
def runSangria(): Unit = {
val future = Future.fromTry(QueryParser.parse(fullIntrospectionQuery))
Await.result(future, 1.minute)
def runSangria(bh: Blackhole): Unit = {
bh.consume(QueryParser.parse(fullIntrospectionQuery).fold(throw _, identity))
()
}

@Benchmark
def runGrackle(): Unit = {
Grackle.compiler.compile(fullIntrospectionQuery)
def runGrackle(bh: Blackhole): Unit = {
bh.consume(Grackle.compiler.compile(fullIntrospectionQuery))
()
}

@Benchmark
def runGql(): Unit = {
gql.parser.parseQuery(fullIntrospectionQuery)
def runGql(bh: Blackhole): Unit = {
bh.consume(gql.parser.parseQuery(fullIntrospectionQuery))
()
}
}
5 changes: 1 addition & 4 deletions core/src/main/scala/caliban/parsing/parsers/Parsers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ import caliban.parsing.adt.Type._
import caliban.parsing.adt._
import fastparse._

import scala.annotation.nowarn

@nowarn("msg=NoWhitespace") // False positive warning in Scala 2.x
object Parsers extends SelectionParsers {
def argumentDefinition(implicit ev: P[Any]): P[InputValueDefinition] =
(stringValue.? ~ name ~ ":" ~ type_ ~ defaultValue.? ~ directives.?).map {
Expand Down Expand Up @@ -348,7 +345,7 @@ object Parsers extends SelectionParsers {
schemaExtension | typeExtension

def definition(implicit ev: P[Any]): P[Definition] =
executableDefinition | typeSystemDefinition | typeSystemExtension
typeSystemDefinition | typeSystemExtension

def document(implicit ev: P[Any]): P[ParsedDocument] =
((Start ~ executableDefinition.rep ~ End) | (Start ~ definition.rep ~ End)).map(seq => ParsedDocument(seq.toList))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,8 @@ import caliban.parsing.adt.Type._
import caliban.parsing.adt._
import fastparse._

import scala.annotation.nowarn

@nowarn("msg=NoWhitespace") // False positive warning in Scala 2.x
private[caliban] trait SelectionParsers extends ValueParsers {

@deprecated("Kept for bincompat only, scheduled to be removed")
def alias(implicit ev: P[Any]): P[String] = name ~ ":"
def aliasOrName(implicit ev: P[Any]): P[String] = ":" ~/ name

def argument(implicit ev: P[Any]): P[(String, InputValue)] = name ~ ":" ~ value
Expand All @@ -30,19 +25,13 @@ private[caliban] trait SelectionParsers extends ValueParsers {
def namedType(implicit ev: P[Any]): P[NamedType] = name.filter(_ != "null").map(NamedType(_, nonNull = false))
def listType(implicit ev: P[Any]): P[ListType] = ("[" ~ type_ ~ "]").map(t => ListType(t, nonNull = false))

@deprecated("Kept for bincompat only, scheduled to be removed")
def nonNullType(implicit ev: P[Any]): P[Type] = ((namedType | listType) ~ "!").map {
case t: NamedType => t.copy(nonNull = true)
case t: ListType => t.copy(nonNull = true)
}

def type_(implicit ev: P[Any]): P[Type] = ((namedType | listType) ~ "!".!.?).map {
case (t: NamedType, nn) => if (nn.isDefined) t.copy(nonNull = true) else t
case (t: ListType, nn) => if (nn.isDefined) t.copy(nonNull = true) else t
}

def field(implicit ev: P[Any]): P[Field] =
(Index ~ name ~ aliasOrName.? ~ arguments.? ~ directives.? ~ selectionSet.?).map {
(Index ~~ name ~ aliasOrName.? ~ arguments.? ~ directives.? ~ selectionSet.?).map {
case (index, alias, Some(name), args, dirs, sels) =>
Field(Some(alias), name, args.getOrElse(Map()), dirs.getOrElse(Nil), sels.getOrElse(Nil), index)
case (index, name, _, args, dirs, sels) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private[caliban] trait StringParsers {

def sourceCharacter(implicit ev: P[Any]): P[Unit] = CharIn("\u0009\u000A\u000D\u0020-\uFFFF")
def sourceCharacterWithoutLineTerminator(implicit ev: P[Any]): P[Unit] = CharIn("\u0009\u0020-\uFFFF")
def name(implicit ev: P[Any]): P[String] = (CharIn("_A-Za-z") ~~ CharIn("_0-9A-Za-z").repX).!
def name(implicit ev: P[Any]): P[String] = !CharIn("0-9") ~~ CharsWhileIn("_0-9A-Za-z", 1).!
def nameOnly(implicit ev: P[Any]): P[String] = Start ~ name ~ End

def hexDigit(implicit ev: P[Any]): P[Unit] = CharIn("0-9a-fA-F")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ import caliban.InputValue._
import caliban.Value._
import fastparse._

import scala.annotation.nowarn

@nowarn("msg=NoWhitespace") // False positive warning in Scala 2.x
private[caliban] trait ValueParsers extends NumberParsers {
def booleanValue(implicit ev: P[Any]): P[BooleanValue] =
StringIn("true", "false").!.map(v => BooleanValue(v.toBoolean))
Expand Down

0 comments on commit c86d2aa

Please sign in to comment.