Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use SWAR technique for more efficient serialization #866

Merged
merged 5 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.plokhotnyuk.jsoniter_scala.core;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;

class ByteArrayAsInt { // FIXME: Use Java wrapper as w/a for missing support of @PolymorphicSignature methods in Scala 3, see: https://github.com/lampepfl/dotty/issues/11332
private static final VarHandle VH_INT =
MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN);

static void set(byte[] buf, int pos, int value) {
VH_INT.set(buf, pos, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.plokhotnyuk.jsoniter_scala.core;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;

class ByteArrayAsLong { // FIXME: Use Java wrapper as w/a for missing support of @PolymorphicSignature methods in Scala 3, see: https://github.com/lampepfl/dotty/issues/11332
private static final VarHandle VH_LONG =
MethodHandles.byteArrayViewVarHandle(long[].class, ByteOrder.LITTLE_ENDIAN);

static void set(byte[] buf, int pos, long value) {
VH_LONG.set(buf, pos, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.github.plokhotnyuk.jsoniter_scala.core;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.nio.ByteOrder;

class ByteArrayAsShort { // FIXME: Use Java wrapper as w/a for missing support of @PolymorphicSignature methods in Scala 3, see: https://github.com/lampepfl/dotty/issues/11332
private static final VarHandle VH_SHORT =
MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN);

static void set(byte[] buf, int pos, short value) {
VH_SHORT.set(buf, pos, value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package com.github.plokhotnyuk.jsoniter_scala.core
import java.io.OutputStream
import java.math.BigInteger
import java.nio.charset.StandardCharsets
import java.nio.{BufferOverflowException, ByteBuffer}
import java.nio.{BufferOverflowException, ByteBuffer, ByteOrder}
import java.time._
import java.util.UUID
import com.github.plokhotnyuk.jsoniter_scala.core.JsonWriter._
import java.lang.invoke.{MethodHandles, VarHandle}
import scala.annotation.tailrec
import scala.{specialized => sp}

Expand Down Expand Up @@ -715,12 +716,7 @@ final class JsonWriter private[jsoniter_scala](
while (offset < lenM1) {
val offsetLim = Math.min(((posLim - pos + 1) >> 1) + offset, lenM1)
while (offset < offsetLim) {
val d1 = ds(bs(offset) & 0xFF)
val d2 = ds(bs(offset + 1) & 0xFF)
buf(pos) = d1.toByte
buf(pos + 1) = (d1 >> 8).toByte
buf(pos + 2) = d2.toByte
buf(pos + 3) = (d2 >> 8).toByte
ByteArrayAsInt.set(buf, pos, ds(bs(offset) & 0xFF) | (ds(bs(offset + 1) & 0xFF) << 16))
pos += 4
offset += 2
}
Expand All @@ -731,9 +727,7 @@ final class JsonWriter private[jsoniter_scala](
}
}
if (offset == lenM1) {
val d1 = ds(bs(offset) & 0xFF)
buf(pos) = d1.toByte
buf(pos + 1) = (d1 >> 8).toByte
ByteArrayAsShort.set(buf, pos, ds(bs(offset) & 0xFF))
pos += 2
}
buf(pos) = '"'
Expand Down Expand Up @@ -865,67 +859,34 @@ final class JsonWriter private[jsoniter_scala](
}

private[this] def writeUUID(mostSigBits: Long, leastSigBits: Long): Unit = count = {
val pos = ensureBufCapacity(38) // 38 == (new java.util.UUID(0, 0)).toString.length + 2
val buf = this.buf
val ds = lowerCaseHexDigits
val pos = ensureBufCapacity(40) // 38 == (new java.util.UUID(0, 0)).toString.length + 2
val buf = this.buf
val mostSigBits1 = (mostSigBits >> 32).toInt
buf(pos) = '"'
val d1 = ds(mostSigBits1 >>> 24)
buf(pos + 1) = d1.toByte
buf(pos + 2) = (d1 >> 8).toByte
val d2 = ds((mostSigBits1 >> 16) & 0xFF)
buf(pos + 3) = d2.toByte
buf(pos + 4) = (d2 >> 8).toByte
val d3 = ds((mostSigBits1 >> 8) & 0xFF)
buf(pos + 5) = d3.toByte
buf(pos + 6) = (d3 >> 8).toByte
val d1 = ds(mostSigBits1 >>> 24) << 8
val d2 = ds((mostSigBits1 >> 16) & 0xFF).toLong << 24
val d3 = ds((mostSigBits1 >> 8) & 0xFF).toLong << 40
val d4 = ds(mostSigBits1 & 0xFF)
buf(pos + 7) = d4.toByte
buf(pos + 8) = (d4 >> 8).toByte
ByteArrayAsLong.set(buf, pos, '"' | d1 | d2 | d3 | d4.toLong << 56)
val mostSigBits2 = mostSigBits.toInt
buf(pos + 9) = '-'
val d5 = ds(mostSigBits2 >>> 24)
buf(pos + 10) = d5.toByte
buf(pos + 11) = (d5 >> 8).toByte
val d6 = ds((mostSigBits2 >> 16) & 0xFF)
buf(pos + 12) = d6.toByte
buf(pos + 13) = (d6 >> 8).toByte
buf(pos + 14) = '-'
val d5 = ds(mostSigBits2 >>> 24) << 16
val d6 = ds((mostSigBits2 >> 16) & 0xFF).toLong << 32
val d7 = ds((mostSigBits2 >> 8) & 0xFF)
buf(pos + 15) = d7.toByte
buf(pos + 16) = (d7 >> 8).toByte
val d8 = ds(mostSigBits2 & 0xFF)
buf(pos + 17) = d8.toByte
buf(pos + 18) = (d8 >> 8).toByte
ByteArrayAsLong.set(buf, pos + 8, d4 >> 8 | d5 | d6 | d7.toLong << 56 | 0x2D000000002D00L) // 0x2D000000002D00L == '-'.toLong << 48 | '-' << 8
val d8 = ds(mostSigBits2 & 0xFF) << 8
val leastSigBits1 = (leastSigBits >> 32).toInt
buf(pos + 19) = '-'
val d9 = ds(leastSigBits1 >>> 24)
buf(pos + 20) = d9.toByte
buf(pos + 21) = (d9 >> 8).toByte
val d10 = ds((leastSigBits1 >> 16) & 0xFF)
buf(pos + 22) = d10.toByte
buf(pos + 23) = (d10 >> 8).toByte
buf(pos + 24) = '-'
val d11 = ds((leastSigBits1 >> 8) & 0xFF)
buf(pos + 25) = d11.toByte
buf(pos + 26) = (d11 >> 8).toByte
val d12 = ds(leastSigBits1 & 0xFF)
buf(pos + 27) = d12.toByte
buf(pos + 28) = (d12 >> 8).toByte
val d9 = ds(leastSigBits1 >>> 24).toLong << 32
val d10 = ds((leastSigBits1 >> 16) & 0xFF).toLong << 48
ByteArrayAsLong.set(buf, pos + 16, d7 >> 8 | d8 | d9 | d10 | 0x2D000000) // 0x2D000000 == '-' << 24
val d11 = ds((leastSigBits1 >> 8) & 0xFF) << 8
val d12 = ds(leastSigBits1 & 0xFF).toLong << 24
val leastSigBits2 = leastSigBits.toInt
val d13 = ds(leastSigBits2 >>> 24)
buf(pos + 29) = d13.toByte
buf(pos + 30) = (d13 >> 8).toByte
val d13 = ds(leastSigBits2 >>> 24).toLong << 40
val d14 = ds((leastSigBits2 >> 16) & 0xFF)
buf(pos + 31) = d14.toByte
buf(pos + 32) = (d14 >> 8).toByte
val d15 = ds((leastSigBits2 >> 8) & 0xFF)
buf(pos + 33) = d15.toByte
buf(pos + 34) = (d15 >> 8).toByte
val d16 = ds(leastSigBits2 & 0xFF)
buf(pos + 35) = d16.toByte
buf(pos + 36) = (d16 >> 8).toByte
buf(pos + 37) = '"'
ByteArrayAsLong.set(buf, pos + 24, '-' | d11 | d12| d13 | d14.toLong << 56)
val d15 = ds((leastSigBits2 >> 8) & 0xFF) << 8
val d16 = ds(leastSigBits2 & 0xFF).toLong << 24
ByteArrayAsLong.set(buf, pos + 32, d14 >> 8 | d15 | d16 | 0x220000000000L) // 0x220000000000L == '"'.toLong << 40
pos + 38
}

Expand Down Expand Up @@ -1217,20 +1178,12 @@ final class JsonWriter private[jsoniter_scala](
}

private[this] def writeBoolean(x: Boolean): Unit = count = {
val pos = ensureBufCapacity(5) // false.toString.length
val buf = this.buf
val pos = ensureBufCapacity(8) // bytes in Long
if (x) {
buf(pos) = 't'
buf(pos + 1) = 'r'
buf(pos + 2) = 'u'
buf(pos + 3) = 'e'
ByteArrayAsInt.set(buf, pos, 0x65757274)
pos + 4
} else {
buf(pos) = 'f'
buf(pos + 1) = 'a'
buf(pos + 2) = 'l'
buf(pos + 3) = 's'
buf(pos + 4) = 'e'
ByteArrayAsLong.set(buf, pos, 0x65736c6166L)
pos + 5
}
}
Expand All @@ -1249,15 +1202,11 @@ final class JsonWriter private[jsoniter_scala](
buf(pos) = (q0 + '0').toByte
pos + 1
} else if (q0 < 100) {
val d = digits(q0)
buf(pos) = d.toByte
buf(pos + 1) = (d >> 8).toByte
ByteArrayAsShort.set(buf, pos, digits(q0))
pos + 2
} else {
val d = digits(q0 - 100)
buf(pos) = '1'
buf(pos + 1) = d.toByte
buf(pos + 2) = (d >> 8).toByte
ByteArrayAsShort.set(buf, pos + 1, digits(q0 - 100))
pos + 3
}
}
Expand Down Expand Up @@ -1671,63 +1620,39 @@ final class JsonWriter private[jsoniter_scala](
}

private[this] def write2Digits(q0: Int, pos: Int, buf: Array[Byte], ds: Array[Short]): Int = {
val d = ds(q0)
buf(pos) = d.toByte
buf(pos + 1) = (d >> 8).toByte
ByteArrayAsShort.set(buf, pos, ds(q0))
pos + 2
}

private[this] def write3Digits(q0: Int, pos: Int, buf: Array[Byte], ds: Array[Short]): Int = {
val q1 = q0 * 1311 >> 17 // divide a small positive int by 100
buf(pos) = (q1 + '0').toByte
val d = ds(q0 - q1 * 100)
buf(pos + 1) = d.toByte
buf(pos + 2) = (d >> 8).toByte
ByteArrayAsShort.set(buf, pos + 1, ds(q0 - q1 * 100))
pos + 3
}

private[this] def write4Digits(q0: Int, pos: Int, buf: Array[Byte], ds: Array[Short]): Int = {
val q1 = q0 * 5243 >> 19 // divide a small positive int by 100
val d1 = ds(q1)
buf(pos) = d1.toByte
buf(pos + 1) = (d1 >> 8).toByte
val d2 = ds(q0 - q1 * 100)
buf(pos + 2) = d2.toByte
buf(pos + 3) = (d2 >> 8).toByte
ByteArrayAsInt.set(buf, pos, ds(q1) | ds(q0 - q1 * 100) << 16)
pos + 4
}

private[this] def write5Digits(q0: Int, pos: Int, buf: Array[Byte], ds: Array[Short]): Int = {
val y0 = q0 * 429497L // James Anhalt's algorithm for 5 digits: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/
buf(pos) = ((y0 >>> 32).toInt + '0').toByte
val y1 = (y0 & 0xFFFFFFFFL) * 100
val d1 = ds((y1 >>> 32).toInt)
buf(pos + 1) = d1.toByte
buf(pos + 2) = (d1 >> 8).toByte
val y2 = (y1 & 0xFFFFFFFFL) * 100
val d2 = ds((y2 >>> 32).toInt)
buf(pos + 3) = d2.toByte
buf(pos + 4) = (d2 >> 8).toByte
ByteArrayAsInt.set(buf, pos + 1, ds((y1 >>> 32).toInt) | ds((y2 >>> 32).toInt) << 16)
pos + 5
}

private[this] def write8Digits(q0: Int, pos: Int, buf: Array[Byte], ds: Array[Short]): Int = {
val y1 = q0 * 140737489L // James Anhalt's algorithm for 8 digits: https://jk-jeon.github.io/posts/2022/02/jeaiii-algorithm/
val d1 = ds((y1 >>> 47).toInt)
buf(pos) = d1.toByte
buf(pos + 1) = (d1 >> 8).toByte
val y2 = (y1 & 0x7FFFFFFFFFFFL) * 100
val d2 = ds((y2 >>> 47).toInt)
buf(pos + 2) = d2.toByte
buf(pos + 3) = (d2 >> 8).toByte
val y3 = (y2 & 0x7FFFFFFFFFFFL) * 100
val d3 = ds((y3 >>> 47).toInt)
buf(pos + 4) = d3.toByte
buf(pos + 5) = (d3 >> 8).toByte
val y4 = (y3 & 0x7FFFFFFFFFFFL) * 100
val d4 = ds((y4 >>> 47).toInt)
buf(pos + 6) = d4.toByte
buf(pos + 7) = (d4 >> 8).toByte
ByteArrayAsLong.set(buf, pos,
ds((y1 >>> 47).toInt) | ds((y2 >>> 47).toInt) << 16 | ds((y3 >>> 47).toInt).toLong << 32 | ds((y4 >>> 47).toInt).toLong << 48)
pos + 8
}

Expand Down Expand Up @@ -2108,9 +2033,7 @@ final class JsonWriter private[jsoniter_scala](
var pos = p
while (pos > posLim) {
val q1 = (q0 * 1374389535L >> 37).toInt // divide a positive int by 100
val d = ds(q0 - q1 * 100)
buf(pos - 1) = d.toByte
buf(pos) = (d >> 8).toByte
ByteArrayAsShort.set(buf, pos - 1, ds(q0 - q1 * 100))
q0 = q1
pos -= 2
}
Expand All @@ -2121,18 +2044,12 @@ final class JsonWriter private[jsoniter_scala](
var pos = p
while (q0 >= 100) {
val q1 = (q0 * 1374389535L >> 37).toInt // divide a positive int by 100
val d = ds(q0 - q1 * 100)
buf(pos - 1) = d.toByte
buf(pos) = (d >> 8).toByte
ByteArrayAsShort.set(buf, pos - 1, ds(q0 - q1 * 100))
q0 = q1
pos -= 2
}
if (q0 < 10) buf(pos) = (q0 + '0').toByte
else {
val d = ds(q0)
buf(pos - 1) = d.toByte
buf(pos) = (d >> 8).toByte
}
else ByteArrayAsShort.set(buf, pos - 1, ds(q0))
}

private[this] def illegalNumberError(x: Double): Nothing = encodeError("illegal number: " + x)
Expand Down