Skip to content

Commit d53ffca

Browse files
committed
JsonStringInput/Output in benchmarks now use raw String/StringBuilder
1 parent 25a5253 commit d53ffca

File tree

4 files changed

+117
-109
lines changed

4 files changed

+117
-109
lines changed

commons-benchmark/src/main/scala/com/avsystem/commons/ser/CirceJsonInputOutput.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.avsystem.commons
22
package ser
33

44
import com.avsystem.commons.serialization.GenCodec.ReadFailure
5-
import com.avsystem.commons.serialization.{FieldInput, GenCodec, Input, InputType, ListInput, ListOutput, ObjectInput, ObjectOutput, Output}
5+
import com.avsystem.commons.serialization._
66
import io.circe.{Json, JsonObject}
77

88
import scala.collection.mutable.ArrayBuffer

commons-benchmark/src/main/scala/com/avsystem/commons/serialization/json/JsonStringInput.scala

Lines changed: 81 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
package com.avsystem.commons
22
package serialization.json
33

4-
import java.io.{Reader, StringReader}
5-
64
import com.avsystem.commons.annotation.explicitGenerics
75
import com.avsystem.commons.serialization.GenCodec.ReadFailure
6+
import com.avsystem.commons.serialization._
87
import com.avsystem.commons.serialization.json.JsonStringInput.{AfterElement, AfterElementNothing}
9-
import com.avsystem.commons.serialization.{FieldInput, GenCodec, Input, InputType, ListInput, ObjectInput}
108

119
object JsonStringInput {
1210
@explicitGenerics def read[T: GenCodec](json: String): T =
13-
GenCodec.read[T](new JsonStringInput(new JsonReader(new StringReader(json))))
11+
GenCodec.read[T](new JsonStringInput(new JsonReader(json)))
1412

1513
private[json] object ObjectMarker {
1614
override def toString = "object"
@@ -79,7 +77,7 @@ class JsonStringInput(reader: JsonReader, callback: AfterElement = AfterElementN
7977
val result = new Array[Byte](hex.length / 2)
8078
var i = 0
8179
while (i < result.length) {
82-
result(i) = ((reader.fromHex(hex.codePointAt(2 * i)) << 4) | reader.fromHex(hex.codePointAt(2 * i + 1))).toByte
80+
result(i) = ((reader.fromHex(hex.charAt(2 * i)) << 4) | reader.fromHex(hex.charAt(2 * i + 1))).toByte
8381
i += 1
8482
}
8583
result
@@ -111,9 +109,10 @@ final class JsonListInput(reader: JsonReader, callback: AfterElement) extends Li
111109
prepareForNext(first = true)
112110

113111
private def prepareForNext(first: Boolean): Unit = {
114-
end = reader.peekNoWs() == ']'
112+
reader.skipWs()
113+
end = reader.isNext(']')
115114
if (end) {
116-
reader.read()
115+
reader.advance()
117116
callback.afterElement()
118117
} else if (!first) {
119118
reader.pass(',')
@@ -134,9 +133,10 @@ final class JsonObjectInput(reader: JsonReader, callback: AfterElement) extends
134133
prepareForNext(first = true)
135134

136135
private def prepareForNext(first: Boolean): Unit = {
137-
end = reader.peekNoWs() == '}'
136+
reader.skipWs()
137+
end = reader.isNext('}')
138138
if (end) {
139-
reader.read()
139+
reader.advance()
140140
callback.afterElement()
141141
} else if (!first) {
142142
reader.pass(',')
@@ -157,56 +157,55 @@ final class JsonObjectInput(reader: JsonReader, callback: AfterElement) extends
157157
prepareForNext(first = false)
158158
}
159159

160-
final class JsonReader(reader: Reader) {
161-
private[this] var peeked: Int = -2
162-
163-
def read(): Int =
164-
if (peeked != -2) {
165-
val res = peeked
166-
peeked = -2
167-
res
168-
} else reader.read()
169-
170-
def peek(): Int =
171-
if (peeked != -2) peeked
172-
else {
173-
peeked = reader.read()
174-
peeked
175-
}
160+
final class JsonReader(json: String) {
161+
private[this] var i: Int = 0
176162

177-
def skipWs(): Unit = {
178-
while (Character.isWhitespace(peek())) {
179-
read()
180-
}
163+
@inline def read(): Char = {
164+
val res = json.charAt(i)
165+
advance()
166+
res
181167
}
182168

183-
def peekNoWs(): Int = {
184-
skipWs()
185-
peek()
169+
@inline def isNext(ch: Char): Boolean =
170+
i < json.length && json.charAt(i) == ch
171+
172+
@inline def isNextDigit: Boolean =
173+
i < json.length && Character.isDigit(json.charAt(i))
174+
175+
@inline def advance(): Unit = {
176+
i += 1
186177
}
187178

188-
def pass(ch: Int): Unit = {
179+
def skipWs(): Unit = {
180+
while (i < json.length && Character.isWhitespace(json.charAt(i))) {
181+
i += 1
182+
}
183+
}
184+
185+
def pass(ch: Char): Unit = {
189186
val r = read()
190187
if (r != ch) throw new ReadFailure(s"'${ch.toChar}' expected, got ${if (r == -1) "EOF" else r.toChar}")
191188
}
192189

193-
def tryPass(ch: Int): Boolean =
194-
if (peek() == ch) {
195-
read()
190+
def tryPass(ch: Char): Boolean =
191+
if (isNext(ch)) {
192+
advance()
196193
true
197194
} else false
198195

199196
private def pass(str: String): Unit = {
200-
var i = 0
201-
while (i < str.length) {
202-
if (read() != str.charAt(i)) {
197+
var j = 0
198+
while (j < str.length) {
199+
if (!isNext(str.charAt(j))) {
203200
throw new ReadFailure(s"expected '$str'")
201+
} else {
202+
advance()
204203
}
205-
i += 1
204+
j += 1
206205
}
207206
}
208207

209-
def fromHex(ch: Int): Int =
208+
def fromHex(ch: Char): Int =
210209
if (ch >= 'A' && ch <= 'F') ch - 'A' + 10
211210
else if (ch >= 'a' && ch <= 'f') ch - 'a' + 10
212211
else if (ch >= '0' && ch <= '9') ch - '0'
@@ -216,56 +215,60 @@ final class JsonReader(reader: Reader) {
216215
fromHex(read())
217216

218217
private def parseNumber(): Any = {
219-
val sb = new JStringBuilder
218+
val start = i
220219
var decimal = false
221220

222-
def advance(): Unit =
223-
sb.appendCodePoint(read())
224-
225-
if (peek() == '-') {
221+
if (isNext('-')) {
226222
advance()
227223
}
228224

229225
def parseDigits(): Unit = {
230-
if (!Character.isDigit(peek())) {
226+
if (!isNextDigit) {
231227
throw new ReadFailure("Expected digit")
232228
}
233-
while (Character.isDigit(peek())) {
229+
while (isNextDigit) {
234230
advance()
235231
}
236232
}
237233

238-
if (peek() == '0') {
234+
if (isNext('0')) {
239235
advance()
240-
} else if (Character.isDigit(peek())) {
236+
} else if (isNextDigit) {
241237
parseDigits()
242238
} else throw new ReadFailure("Expected '-' or digit")
243239

244-
if (peek() == '.') {
240+
if (isNext('.')) {
245241
decimal = true
246242
advance()
247243
parseDigits()
248-
if (peek() == 'e' || peek() == 'E') {
244+
if (isNext('e') || isNext('E')) {
249245
advance()
250-
if (peek() == '-' || peek() == '+') {
246+
if (isNext('-') || isNext('+')) {
251247
advance()
252248
parseDigits()
253249
} else throw new ReadFailure("Expected '+' or '-'")
254250
}
255251
}
256252

257-
val str = sb.toString
253+
val str = json.substring(start, i)
258254
if (decimal) str.toDouble else str.toInt
259255
}
260256

261257
def parseString(): String = {
262258
pass('"')
263-
val sb = new JStringBuilder
259+
var sb: JStringBuilder = null
260+
var plainStart = i
264261
var cont = true
265262
while (cont) {
266263
read() match {
267264
case '"' => cont = false
268265
case '\\' =>
266+
if (sb == null) {
267+
sb = new JStringBuilder
268+
}
269+
if (plainStart < i - 1) {
270+
sb.append(json, plainStart, i - 1)
271+
}
269272
val unesc = read() match {
270273
case '"' => '"'
271274
case '\\' => '\\'
@@ -277,22 +280,32 @@ final class JsonReader(reader: Reader) {
277280
case 'u' => ((readHex() << 12) + (readHex() << 8) + (readHex() << 4) + readHex()).toChar
278281
}
279282
sb.append(unesc)
280-
case c =>
281-
sb.append(c.toChar)
283+
plainStart = i
284+
case _ =>
282285
}
283286
}
284-
sb.toString
287+
if (sb != null) {
288+
sb.append(json, plainStart, i - 1)
289+
sb.toString
290+
} else {
291+
json.substring(plainStart, i - 1)
292+
}
285293
}
286294

287-
def parseValue(): Any = peekNoWs() match {
288-
case '"' => parseString()
289-
case 't' => pass("true"); true
290-
case 'f' => pass("false"); false
291-
case 'n' => pass("null"); null
292-
case '[' => read(); JsonStringInput.ListMarker
293-
case '{' => read(); JsonStringInput.ObjectMarker
294-
case '-' => parseNumber()
295-
case c if Character.isDigit(c) => parseNumber()
296-
case c => throw new ReadFailure(s"Unexpected character: '${c.toChar}'")
295+
def parseValue(): Any = {
296+
skipWs()
297+
if (i < json.length) json.charAt(i) match {
298+
case '"' => parseString()
299+
case 't' => pass("true"); true
300+
case 'f' => pass("false"); false
301+
case 'n' => pass("null"); null
302+
case '[' => read(); JsonStringInput.ListMarker
303+
case '{' => read(); JsonStringInput.ObjectMarker
304+
case '-' => parseNumber()
305+
case c if Character.isDigit(c) => parseNumber()
306+
case c => throw new ReadFailure(s"Unexpected character: '${c.toChar}'")
307+
} else {
308+
throw new ReadFailure("EOF")
309+
}
297310
}
298311
}
Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
11
package com.avsystem.commons
22
package serialization.json
33

4-
import java.io.{StringWriter, Writer}
5-
64
import com.avsystem.commons.serialization.{GenCodec, ListOutput, ObjectOutput, Output}
75

86
object JsonStringOutput {
97
def write[T: GenCodec](value: T): String = {
10-
val sw = new StringWriter
11-
GenCodec.write[T](new JsonStringOutput(sw), value)
12-
sw.toString
8+
val sb = new JStringBuilder
9+
GenCodec.write[T](new JsonStringOutput(sb), value)
10+
sb.toString
1311
}
1412
}
1513

1614
abstract class BaseJsonOutput {
17-
protected final def writeJsonString(writer: Writer, str: String): Unit = {
18-
writer.write('"')
15+
protected final def writeJsonString(builder: JStringBuilder, str: String): Unit = {
16+
builder.append('"')
1917
var i = 0
2018
var s = 0
2119
while (i < str.length) {
@@ -31,69 +29,68 @@ abstract class BaseJsonOutput {
3129
case _ => null
3230
}
3331
if (esc != null) {
34-
writer.write(str, s, i - s)
35-
writer.write(esc)
32+
builder.append(str, s, i).append(esc)
3633
s = i + 1
3734
}
3835
i += 1
3936
}
40-
writer.write(str, s, str.length - s)
41-
writer.write('"')
37+
builder.append(str, s, str.length)
38+
builder.append('"')
4239
}
4340
}
4441

45-
final class JsonStringOutput(writer: Writer) extends BaseJsonOutput with Output {
46-
def writeNull(): Unit = writer.write("null")
47-
def writeString(str: String): Unit = writeJsonString(writer, str)
48-
def writeBoolean(boolean: Boolean): Unit = writer.write(boolean.toString)
49-
def writeInt(int: Int): Unit = writer.write(int.toString)
42+
final class JsonStringOutput(builder: JStringBuilder) extends BaseJsonOutput with Output {
43+
def writeNull(): Unit = builder.append("null")
44+
def writeString(str: String): Unit = writeJsonString(builder, str)
45+
def writeBoolean(boolean: Boolean): Unit = builder.append(boolean.toString)
46+
def writeInt(int: Int): Unit = builder.append(int.toString)
5047
def writeLong(long: Long): Unit = {
5148
val asInt = long.toInt
5249
if (asInt == long) writeInt(asInt)
5350
else writeString(long.toString)
5451
}
55-
def writeDouble(double: Double): Unit = writer.write(double.toString)
52+
def writeDouble(double: Double): Unit = builder.append(double.toString)
5653
def writeBinary(binary: Array[Byte]): Unit = {
57-
writer.write('"')
54+
builder.append('"')
5855
var i = 0
5956
while (i < binary.length) {
60-
writer.write(f"${binary(i)}%02x")
57+
builder.append(f"${binary(i)}%02x")
6158
i += 1
6259
}
63-
writer.write('"')
60+
builder.append('"')
6461
}
65-
def writeList(): ListOutput = new JsonListOutput(writer)
66-
def writeObject(): ObjectOutput = new JsonObjectOutput(writer)
62+
def writeList(): ListOutput = new JsonListOutput(builder)
63+
def writeObject(): ObjectOutput = new JsonObjectOutput(builder)
6764
}
6865

69-
final class JsonListOutput(writer: Writer) extends ListOutput {
66+
final class JsonListOutput(builder: JStringBuilder) extends ListOutput {
7067
private[this] var first = true
7168
def writeElement(): Output = {
72-
writer.write(if (first) '[' else ',')
69+
builder.append(if (first) '[' else ',')
7370
first = false
74-
new JsonStringOutput(writer)
71+
new JsonStringOutput(builder)
7572
}
7673
def finish(): Unit = {
7774
if (first) {
78-
writer.write('[')
75+
builder.append('[')
7976
}
80-
writer.write(']')
77+
builder.append(']')
8178
}
8279
}
8380

84-
final class JsonObjectOutput(writer: Writer) extends BaseJsonOutput with ObjectOutput {
81+
final class JsonObjectOutput(builder: JStringBuilder) extends BaseJsonOutput with ObjectOutput {
8582
private[this] var first = true
8683
def writeField(key: String): Output = {
87-
writer.write(if (first) '{' else ',')
84+
builder.append(if (first) '{' else ',')
8885
first = false
89-
writeJsonString(writer, key)
90-
writer.write(':')
91-
new JsonStringOutput(writer)
86+
writeJsonString(builder, key)
87+
builder.append(':')
88+
new JsonStringOutput(builder)
9289
}
9390
def finish(): Unit = {
9491
if (first) {
95-
writer.write('{')
92+
builder.append('{')
9693
}
97-
writer.write('}')
94+
builder.append('}')
9895
}
9996
}

0 commit comments

Comments
 (0)