zio / zio-json

Fast, secure JSON library with tight ZIO integration.
https://zio.dev/zio-json
Apache License 2.0
406 stars 146 forks source link

Async engine for ZIO JSON #576

Open jdegoes opened 2 years ago

jdegoes commented 2 years ago

Given the Loom is still a ways out, it can make sense to investigate an async engine for JSON to see if we can achieve performance in the ballpark of the sync engine in the happy path.

Such an async engine would be push-based rather than _pull-based. This means that instead of pulling new characters from a Reader, it would be pushed characters a block at a time.

A push-based engine could adopt parallel parsing. Parallel parsing is required for fallback, but also for parsing multiple fields at the same time.

TODO

jdegoes commented 2 years ago

class Registers(registers: Array[Any])

trait AsyncDecoding[+Value] {
  /**
   * Resets all state of the decoding to the initial values, such that the 
   * decoding is equivalent to a new one. This method can be used by single-fibered
   * code in order to accelerate performance and avoid re-creating decodings for
   * the same type of data.
   */
  def reset(): Unit 

  /**
   * Returns the number of characters consumed. If this is equal to the length of the 
   * char sequence, then the full length was consumed, but no more data is desired,
   * because the decoding processs is finished. On the other hand, if this is greater 
   * than the length of the char sequence, then the full length was consumed, and 
   * more data is desired. If this is less than the length of the char sequence, then 
   * no more data is required, and the decoding stopped short of consuming all characters 
   * in the char sequence. If there is an error, then an `AsyncDecodingError` is thrown.
   */
  def feed(chars: CharSequence): Int
}

final case class AsyncDecodingError(message: String, path: List[String] = Nil) extends Exception(message) with NoStackTrace

trait AsyncDecoder[+Value] {
  def unsafeNewDecoding(index: Int, registers: Registers): AsyncDecoding[Value]
}
object AsyncDecoder {
  val boolean: AsyncDecoder[Boolean] = 
    (index: Int, registers: Registers) => 
      new AsyncDecoding[Boolean] {
        var state: Int = 0 

        val BranchMask   = 0x3
        val BranchBitLen = 2 

        def branch(): Int = (state & BranchMask)
        def position(): Int = (state >> BranchBitLen)

        val Undecided = 1 
        val IsTrue    = 2 
        val IsFalse   = 3

        def setBranch(branch: Int): Unit = state = (state & ~BranchMask) | branch
        def setPosition(int: Int): Unit = state = (state & BranchMask) | (int << BranchBitLen)
        def setBranchAndPosition(branch: Int, pos: Int): Unit = state = branch | (pos << BranchBitLen)

        // 't' 'r' 'u' 'e'
        // 'f' 'a' 'l' 's' 'e'
        def reset(): Unit = state = 0

        def feed(chars: CharSequence): Int =
          if (chars.length == 0) ???
          else {
            branch() match {
              case Undecided => 
                chars.charAt(0) match {
                  case 't' => 
                    // TODO: Do NOT modify the state if we can read it all at once (the happy path!)
                    // TODO: Read all the chars, or pause when we get as far as we can:
                    setBranchAndPosition(IsTrue, 1)

                    ???
                  case 'f' => 
                    // TODO: Do NOT modify the state if we can read it all at once (the happy path!)
                    // TODO: Read all the chars, or pause when we get as far as we can:
                    setBranchAndPosition(IsFalse, 1)

                    ???
                  case _ => throw AsyncDecodingError("""Expected "true" or "false" boolean literal""")
                }
              case IsTrue => 
                val curPos = position()

                if (curPos == 4) registers.registers(index) = true 

                ???
              case IsFalse => 
                val curPos = position()

                ???
            }
          }
      }
}
jkobejs commented 2 years ago

I'll take it

fsvehla commented 2 years ago

@jkobejs Awesome! Please make sure to target the zio2 branch.