Skip to content

Commit

Permalink
Fix performance issue of MappedByteBuffers (#3521)
Browse files Browse the repository at this point in the history
* Optimize `java.nio.ByteArrayBits` to not allocate intermidiete tuples
* Hint inlining of ByteArrayBits
* Add Character.reverseBytes implementaiton
* Sync templates with code generated types in runtime.Arrays , unsafe.Size and unsigned.USize
* Handle CFuncPtr.apply when inlined

(cherry picked from commit adcad38)
  • Loading branch information
WojciechMazur committed Oct 13, 2023
1 parent 312600c commit 0250712
Show file tree
Hide file tree
Showing 19 changed files with 215 additions and 312 deletions.
6 changes: 5 additions & 1 deletion javalib/src/main/scala/java/lang/Character.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package java.lang

import java.util.Arrays
import java.lang.constant.Constable
import scala.scalanative.runtime.LLVMIntrinsics

class Character(val _value: scala.Char)
extends _Object
Expand Down Expand Up @@ -2633,11 +2634,14 @@ object Character {
null
}
}

def reverseBytes(ch: scala.Char): scala.Char =
LLVMIntrinsics.`llvm.bswap.i16`(ch.toShort).toChar

// TODO:
// def getDirectionality(c: scala.Char): scala.Byte
// def toTitleCase(c: scala.Char): scala.Char
// def getNumericValue(c: scala.Char): Int
// def reverseBytes(ch: scala.Char): scala.Char
// ...
}

Expand Down
318 changes: 93 additions & 225 deletions javalib/src/main/scala/java/nio/ByteArrayBits.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package java.nio

import scala.scalanative.unsafe.Ptr
import scala.scalanative.runtime.{Intrinsics, toRawPtr}
import scala.scalanative.runtime.Intrinsics.{elemRawPtr, castIntToRawSize}
import scala.scalanative.unsafe._

// Ported from Scala.js
private[nio] object ByteArrayBits {
def apply(
array: Ptr[Byte],
Expand All @@ -21,249 +22,116 @@ private[nio] final class ByteArrayBits(
indexMultiplier: Int
) {

/* We use tuples of bytes instead of, say, arrays, because they can be
* completely stack-allocated.
*
* When used in a place where it can be stack-allocated, an "instance" of
* this class has zero overhead.
*/

// API

def loadChar(index: Int): Char = makeChar(load2Bytes(index))
def loadShort(index: Int): Short = makeShort(load2Bytes(index))
def loadInt(index: Int): Int = makeInt(load4Bytes(index))
def loadLong(index: Int): Long = makeLong(load8Bytes(index))
def loadFloat(index: Int): Float = makeFloat(load4Bytes(index))
def loadDouble(index: Int): Double = makeDouble(load8Bytes(index))

def storeChar(index: Int, v: Char): Unit = store2Bytes(index, unmakeChar(v))
def storeShort(index: Int, v: Short): Unit =
store2Bytes(index, unmakeShort(v))
def storeInt(index: Int, v: Int): Unit = store4Bytes(index, unmakeInt(v))
def storeLong(index: Int, v: Long): Unit = store8Bytes(index, unmakeLong(v))
def storeFloat(index: Int, v: Float): Unit =
store4Bytes(index, unmakeFloat(v))
def storeDouble(index: Int, v: Double): Unit =
store8Bytes(index, unmakeDouble(v))

// Making and unmaking values

@inline
private def makeChar(bs: (Byte, Byte)): Char =
makeChar(bs._1, bs._2)

@inline
private def makeChar(b0: Byte, b1: Byte): Char =
if (isBigEndian) makeCharBE(b0, b1)
else makeCharBE(b1, b0)

@inline
private def makeCharBE(b0: Byte, b1: Byte): Char =
((b0 << 8) | (b1 & 0xff)).toChar

@inline
private def makeShort(bs: (Byte, Byte)): Short =
makeShort(bs._1, bs._2)

@inline
private def makeShort(b0: Byte, b1: Byte): Short =
if (isBigEndian) makeShortBE(b0, b1)
else makeShortBE(b1, b0)

@inline
private def makeShortBE(b0: Byte, b1: Byte): Short =
((b0 << 8) | (b1 & 0xff)).toShort

@inline
private def makeInt(bs: (Byte, Byte, Byte, Byte)): Int =
makeInt(bs._1, bs._2, bs._3, bs._4)

@inline
private def makeInt(b0: Byte, b1: Byte, b2: Byte, b3: Byte): Int =
if (isBigEndian) makeIntBE(b0, b1, b2, b3)
else makeIntBE(b3, b2, b1, b0)

@inline
private def makeIntBE(b0: Byte, b1: Byte, b2: Byte, b3: Byte): Int =
((b0 << 24) | ((b1 & 0xff) << 16) | ((b2 & 0xff) << 8) | (b3 & 0xff))

@inline
private def makeLong(
bs: (Byte, Byte, Byte, Byte, Byte, Byte, Byte, Byte)
): Long =
makeLong(bs._1, bs._2, bs._3, bs._4, bs._5, bs._6, bs._7, bs._8)

@inline
private def makeLong(
b0: Byte,
b1: Byte,
b2: Byte,
b3: Byte,
b4: Byte,
b5: Byte,
b6: Byte,
b7: Byte
): Long =
if (isBigEndian) makeLongBE(b0, b1, b2, b3, b4, b5, b6, b7)
else makeLongBE(b7, b6, b5, b4, b3, b2, b1, b0)

@inline
private def makeLongBE(
b0: Byte,
b1: Byte,
b2: Byte,
b3: Byte,
b4: Byte,
b5: Byte,
b6: Byte,
b7: Byte
): Long = {
(makeIntBE(b0, b1, b2, b3).toLong << 32) |
(makeIntBE(b4, b5, b6, b7).toLong & 0xffffffffL)
def loadChar(index: Int): Char = {
val idx = indexMultiplier * index + arrayOffset
val loaded =
Intrinsics.loadChar(elemRawPtr(toRawPtr(array), castIntToRawSize(idx)))
if (isBigEndian) java.lang.Character.reverseBytes(loaded)
else loaded
}

@inline
private def makeFloat(bs: (Byte, Byte, Byte, Byte)): Float =
makeFloat(bs._1, bs._2, bs._3, bs._4)

@inline
private def makeFloat(b0: Byte, b1: Byte, b2: Byte, b3: Byte): Float =
java.lang.Float.intBitsToFloat(makeInt(b0, b1, b2, b3))

@inline
private def makeDouble(
bs: (Byte, Byte, Byte, Byte, Byte, Byte, Byte, Byte)
): Double =
makeDouble(bs._1, bs._2, bs._3, bs._4, bs._5, bs._6, bs._7, bs._8)

@inline
private def makeDouble(
b0: Byte,
b1: Byte,
b2: Byte,
b3: Byte,
b4: Byte,
b5: Byte,
b6: Byte,
b7: Byte
): Double =
java.lang.Double.longBitsToDouble(makeLong(b0, b1, b2, b3, b4, b5, b6, b7))

@inline
private def unmakeChar(c: Char): (Byte, Byte) = {
val bs = unmakeCharBE(c)
if (isBigEndian) bs
else (bs._2, bs._1)
def loadShort(index: Int): Short = {
val idx = indexMultiplier * index + arrayOffset
val loaded =
Intrinsics.loadShort(elemRawPtr(toRawPtr(array), castIntToRawSize(idx)))
if (isBigEndian) java.lang.Short.reverseBytes(loaded)
else loaded
}

@inline
private def unmakeCharBE(c: Char): (Byte, Byte) =
((c >> 8).toByte, c.toByte)

@inline
private def unmakeShort(s: Short): (Byte, Byte) = {
val bs = unmakeShortBE(s)
if (isBigEndian) bs
else (bs._2, bs._1)
def loadInt(index: Int): Int = {
val idx = indexMultiplier * index + arrayOffset
val loaded =
Intrinsics.loadInt(elemRawPtr(toRawPtr(array), castIntToRawSize(idx)))
if (isBigEndian) java.lang.Integer.reverseBytes(loaded)
else loaded
}

@inline
private def unmakeShortBE(s: Short): (Byte, Byte) =
((s >> 8).toByte, s.toByte)

@inline
private def unmakeInt(i: Int): (Byte, Byte, Byte, Byte) = {
val bs = unmakeIntBE(i)
if (isBigEndian) bs
else (bs._4, bs._3, bs._2, bs._1)
def loadLong(index: Int): Long = {
val idx = indexMultiplier * index + arrayOffset
val loaded =
Intrinsics.loadLong(elemRawPtr(toRawPtr(array), castIntToRawSize(idx)))
if (isBigEndian) java.lang.Long.reverseBytes(loaded)
else loaded
}

@inline
private def unmakeIntBE(i: Int): (Byte, Byte, Byte, Byte) =
((i >> 24).toByte, (i >> 16).toByte, (i >> 8).toByte, i.toByte)

@inline
private def unmakeLong(
l: Long
): (Byte, Byte, Byte, Byte, Byte, Byte, Byte, Byte) = {
val bs0 = unmakeIntBE((l >>> 32).toInt)
val bs1 = unmakeIntBE(l.toInt)
if (isBigEndian)
(bs0._1, bs0._2, bs0._3, bs0._4, bs1._1, bs1._2, bs1._3, bs1._4)
else (bs1._4, bs1._3, bs1._2, bs1._1, bs0._4, bs0._3, bs0._2, bs0._1)
def loadFloat(index: Int): Float = {
val idx = indexMultiplier * index + arrayOffset
val loaded =
Intrinsics.loadInt(elemRawPtr(toRawPtr(array), castIntToRawSize(idx)))
val ordered =
if (isBigEndian) java.lang.Integer.reverseBytes(loaded)
else loaded
java.lang.Float.intBitsToFloat(ordered)
}

@inline
private def unmakeFloat(f: Float): (Byte, Byte, Byte, Byte) =
unmakeInt(java.lang.Float.floatToIntBits(f))

@inline
private def unmakeDouble(
d: Double
): (Byte, Byte, Byte, Byte, Byte, Byte, Byte, Byte) =
unmakeLong(java.lang.Double.doubleToLongBits(d))

// Loading and storing bytes

@inline
private def load2Bytes(index: Int): (Byte, Byte) = {
def loadDouble(index: Int): Double = {
val idx = indexMultiplier * index + arrayOffset
(array(idx), array(idx + 1))
val loaded =
Intrinsics.loadLong(elemRawPtr(toRawPtr(array), castIntToRawSize(idx)))
val ordered =
if (isBigEndian) java.lang.Long.reverseBytes(loaded)
else loaded
java.lang.Double.longBitsToDouble(ordered)
}

@inline
private def load4Bytes(index: Int): (Byte, Byte, Byte, Byte) = {
def storeChar(index: Int, v: Char): Unit = {
val idx = indexMultiplier * index + arrayOffset
(array(idx), array(idx + 1), array(idx + 2), array(idx + 3))
val ordered =
if (isBigEndian) java.lang.Character.reverseBytes(v)
else v
Intrinsics.storeChar(
elemRawPtr(toRawPtr(array), castIntToRawSize(idx)),
ordered
)
}

@inline
private def load8Bytes(
index: Int
): (Byte, Byte, Byte, Byte, Byte, Byte, Byte, Byte) = {
def storeShort(index: Int, v: Short): Unit = {
val idx = indexMultiplier * index + arrayOffset
(
array(idx),
array(idx + 1),
array(idx + 2),
array(idx + 3),
array(idx + 4),
array(idx + 5),
array(idx + 6),
array(idx + 7)
val ordered =
if (isBigEndian) java.lang.Short.reverseBytes(v)
else v
Intrinsics.storeShort(
elemRawPtr(toRawPtr(array), castIntToRawSize(idx)),
ordered
)
}

@inline
private def store2Bytes(index: Int, bs: (Byte, Byte)): Unit = {
def storeInt(index: Int, v: Int): Unit = {
val idx = indexMultiplier * index + arrayOffset
array(idx) = bs._1
array(idx + 1) = bs._2
val ordered =
if (isBigEndian) java.lang.Integer.reverseBytes(v)
else v
Intrinsics.storeInt(
elemRawPtr(toRawPtr(array), castIntToRawSize(idx)),
ordered
)
}

@inline
private def store4Bytes(index: Int, bs: (Byte, Byte, Byte, Byte)): Unit = {
def storeLong(index: Int, v: Long): Unit = {
val idx = indexMultiplier * index + arrayOffset
array(idx) = bs._1
array(idx + 1) = bs._2
array(idx + 2) = bs._3
array(idx + 3) = bs._4
val ordered =
if (isBigEndian) java.lang.Long.reverseBytes(v)
else v
Intrinsics.storeLong(
elemRawPtr(toRawPtr(array), castIntToRawSize(idx)),
ordered
)
}

@inline
private def store8Bytes(
index: Int,
bs: (Byte, Byte, Byte, Byte, Byte, Byte, Byte, Byte)
): Unit = {
def storeFloat(index: Int, v: Float): Unit = {
val idx = indexMultiplier * index + arrayOffset
array(idx) = bs._1
array(idx + 1) = bs._2
array(idx + 2) = bs._3
array(idx + 3) = bs._4
array(idx + 4) = bs._5
array(idx + 5) = bs._6
array(idx + 6) = bs._7
array(idx + 7) = bs._8
val asInt = java.lang.Float.floatToIntBits(v)
val ordered =
if (isBigEndian) java.lang.Integer.reverseBytes(asInt)
else asInt
Intrinsics.storeInt(
elemRawPtr(toRawPtr(array), castIntToRawSize(idx)),
ordered
)
}
def storeDouble(index: Int, v: Double): Unit = {
val idx = indexMultiplier * index + arrayOffset
val asLong = java.lang.Double.doubleToLongBits(v)
val ordered =
if (isBigEndian) java.lang.Long.reverseBytes(asLong)
else asLong
Intrinsics.storeLong(
elemRawPtr(toRawPtr(array), castIntToRawSize(idx)),
ordered
)
}
}

0 comments on commit 0250712

Please sign in to comment.