Slices and string cache

This commit is contained in:
2024-09-06 10:25:15 +02:00
parent 6567da1683
commit 85c3dd8292
7 changed files with 285 additions and 90 deletions

View File

@@ -6,7 +6,7 @@ plugins {
}
group = "nl.astraeus"
version = "0.1.0-SNAPSHOT"
version = "0.1.1-SNAPSHOT"
repositories {
mavenCentral()
@@ -137,6 +137,32 @@ tasks.named<Task>("publishKotlinMultiplatformPublicationToMavenLocal") {
dependsOn(tasks.named<Task>("signJsPublication"))
}
tasks.named<Task>("publishJsPublicationToMavenLocalRepository") {
dependsOn(tasks.named<Task>("signJvmPublication"))
}
tasks.named<Task>("publishJvmPublicationToMavenLocalRepository") {
dependsOn(tasks.named<Task>("signJsPublication"))
}
tasks.named<Task>("publishJvmPublicationToMavenLocalRepository") {
dependsOn(tasks.named<Task>("signKotlinMultiplatformPublication"))
}
tasks.named<Task>("publishJsPublicationToMavenLocalRepository") {
dependsOn(tasks.named<Task>("signKotlinMultiplatformPublication"))
}
tasks.named<Task>("publishKotlinMultiplatformPublicationToMavenLocalRepository") {
dependsOn(tasks.named<Task>("signJvmPublication"))
}
tasks.named<Task>("publishKotlinMultiplatformPublicationToMavenLocalRepository") {
dependsOn(tasks.named<Task>("signJsPublication"))
}
tasks.named<Task>("publishJsPublicationToGiteaRepository") {
dependsOn(tasks.named<Task>("signJvmPublication"))
}
@@ -159,4 +185,4 @@ tasks.named<Task>("publishKotlinMultiplatformPublicationToGiteaRepository") {
tasks.named<Task>("publishKotlinMultiplatformPublicationToGiteaRepository") {
dependsOn(tasks.named<Task>("signJsPublication"))
}
}

View File

@@ -12,7 +12,8 @@ enum class DataType(
DOUBLE(8),
STRING(-1, 2), // max length 65535
CLOB(-1, 4), // max length 2^32-1
BLOB(-1, 4), // max length 2^32-1
BLOB(-1, 4),
//SLICE(-1, 4) // max length 2^32-1
;
}

View File

@@ -13,61 +13,20 @@ class MutableByteArrayHandler(
buffer[index] = value
}
fun setShort(index: Int, value: Short) {
buffer[index] = (value.toInt() shr 8).toByte()
buffer[index + 1] = value.toByte()
}
fun setShort(index: Int, value: Short) = buffer.setShort(index, value)
fun setInt(index: Int, value: Int) {
buffer[index] = ((value shr 24) and 0xff).toByte()
buffer[index + 1] = ((value shr 16) and 0xff).toByte()
buffer[index + 2] = ((value shr 8) and 0xff).toByte()
buffer[index + 3] = (value and 0xff).toByte()
}
fun setInt(index: Int, value: Int) = buffer.setInt(index, value)
fun setLong(index: Int, value: Long) {
buffer[index] = ((value shr 56) and 0xff).toByte()
buffer[index + 1] = ((value shr 48) and 0xff).toByte()
buffer[index + 2] = ((value shr 40) and 0xff).toByte()
buffer[index + 3] = ((value shr 32) and 0xff).toByte()
buffer[index + 4] = ((value shr 24) and 0xff).toByte()
buffer[index + 5] = ((value shr 16) and 0xff).toByte()
buffer[index + 6] = ((value shr 8) and 0xff).toByte()
buffer[index + 7] = (value and 0xff).toByte()
}
fun setLong(index: Int, value: Long) = buffer.setLong(index, value)
fun setFloat(index: Int, value: Float) = setInt(index, value.toRawBits())
fun setDouble(index: Int, value: Double) = setLong(index, value.toRawBits())
fun setFloat(index: Int, value: Float) = buffer.setFloat(index, value)
fun setDouble(index: Int, value: Double) = buffer.setDouble(index, value)
fun setString(index: Int, value: String, maxLength: Int): Int {
val bytes = value.encodeToByteArray()
if (bytes.size > maxLength) {
throw IllegalArgumentException("String is too long")
}
this.setShort(index, bytes.size.toShort())
bytes.copyInto(buffer.data, index + 2)
return index + bytes.size + 2
}
fun setString(index: Int, value: String, maxLength: Int) = buffer.setString(index, value, maxLength)
fun setClob(index: Int, value: String, maxLength: Int): Int {
val bytes = value.encodeToByteArray()
if (bytes.size > maxLength) {
throw IllegalArgumentException("String is too long")
}
this.setInt(index, bytes.size)
bytes.copyInto(buffer.data, index + 4)
return index + bytes.size + 4
}
fun setBlob(index: Int, bytes: ByteArray, maxLength: Int): Int {
if (bytes.size > maxLength) {
throw IllegalArgumentException("String is too long")
}
this.setInt(index, bytes.size)
bytes.copyInto(buffer.data, index + 4)
return index + bytes.size + 4
}
fun setClob(index: Int, value: String, maxLength: Int) = buffer.setClob(index, value, maxLength)
fun setBlob(index: Int, bytes: SlicedByteArray, maxLength: Int) = buffer.setBlob(index, bytes, maxLength)
}
open class ByteArrayHandler(
@@ -83,18 +42,9 @@ open class ByteArrayHandler(
return buffer[index]
}
fun getShort(index: Int): Short {
return ((buffer[index].toInt() shl 8) or (buffer[index + 1].toInt() and 0xff)).toShort()
}
fun getShort(index: Int) = buffer.getShort(index)
fun getInt(index: Int): Int {
return (
(buffer[index].toInt() shl 24) or
((buffer[index+1].toInt() shl 16) and 0xff0000) or
((buffer[index+2].toInt() shl 8) and 0xff00) or
(buffer[index+3].toInt() and 0xff)
)
}
fun getInt(index: Int) = buffer.getInt(index)
fun getLong(index: Int): Long {
return (
@@ -109,26 +59,14 @@ open class ByteArrayHandler(
)
}
fun getFloat(index: Int): Float = Float.fromBits(getInt(index))
fun getDouble(index: Int): Double = Double.fromBits(getLong(index))
fun getFloat(index: Int): Float = buffer.getFloat(index)
fun getDouble(index: Int): Double = buffer.getDouble(index)
fun getString(index: Int): String {
val length = getShort(index)
val str = buffer.data.copyOfRange(index + 2, index + 2 + length).decodeToString()
return str
}
fun getString(index: Int) = buffer.getString(index)
fun getClob(index: Int): String {
val length = getInt(index)
val str = buffer.data.copyOfRange(index + 4, index + 4 + length).decodeToString()
return str
}
fun getClob(index: Int): String = buffer.getClob(index)
fun getBlob(index: Int): ByteArray {
val length = getInt(index)
val str = buffer.data.copyOfRange(index + 4, index + 4 + length)
return str
}
fun getBlob(index: Int): SlicedByteArray = buffer.getBlob(index)
fun slice(range: IntRange): ByteArrayHandler {
return ByteArrayHandler(

View File

@@ -117,8 +117,9 @@ class DoubleProperty(
}
class StringProperty(
name: String
name: String,
) : ByteArrayPropertyWithLength<String>(name) {
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): String {
return thisRef.data.getString(getIndex(thisRef))
}
@@ -128,6 +129,24 @@ class StringProperty(
}
}
class CachedStringProperty(
name: String,
) : ByteArrayPropertyWithLength<String>(name) {
var cachedValue: String? = null
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): String {
if (cachedValue == null) {
cachedValue = thisRef.data.getString(getIndex(thisRef))
}
return cachedValue!!
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: String) {
thisRef.data.setString(getIndex(thisRef), value, getMaxLength(thisRef))
cachedValue = value
}
}
class ClobProperty(
name: String
) : ByteArrayPropertyWithLength<String>(name) {
@@ -142,12 +161,12 @@ class ClobProperty(
class BlobProperty(
name: String
) : ByteArrayPropertyWithLength<ByteArray>(name) {
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): ByteArray {
) : ByteArrayPropertyWithLength<SlicedByteArray>(name) {
override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): SlicedByteArray {
return thisRef.data.getBlob(getIndex(thisRef))
}
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: ByteArray) {
override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: SlicedByteArray) {
thisRef.data.setBlob(getIndex(thisRef), value, getMaxLength(thisRef))
}
}
@@ -160,5 +179,6 @@ fun long(propertyName: String) = LongProperty(propertyName)
fun float(propertyName: String) = FloatProperty(propertyName)
fun double(propertyName: String) = DoubleProperty(propertyName)
fun string(propertyName: String) = StringProperty(propertyName)
fun cachedString(propertyName: String) = CachedStringProperty(propertyName)
fun clob(propertyName: String) = ClobProperty(propertyName)
fun blob(propertyName: String) = BlobProperty(propertyName)

View File

@@ -16,15 +16,141 @@ class SlicedByteArray(
data[offset + index] = value
}
fun setShort(index: Int, value: Short) {
check(offset + index + 1 < offset + length) { "Index out of bounds" }
data[offset + index] = (value.toInt() shr 8).toByte()
data[offset + index + 1] = value.toByte()
}
fun getShort(index: Int): Short {
check(offset + index + 1 < offset + length) { "Index out of bounds" }
return ((data[offset + index].toInt() shl 8) or (data[offset + index + 1].toInt() and 0xff)).toShort()
}
fun setInt(index: Int, value: Int) {
check(offset + index + 3 < offset + length) { "Index out of bounds" }
data[offset + index] = ((value shr 24) and 0xff).toByte()
data[offset + index + 1] = ((value shr 16) and 0xff).toByte()
data[offset + index + 2] = ((value shr 8) and 0xff).toByte()
data[offset + index + 3] = (value and 0xff).toByte()
}
fun getInt(index: Int): Int {
check(offset + index + 3 < offset + length) { "Index out of bounds" }
return (
(data[offset + index].toInt() shl 24) or
((data[offset + index+1].toInt() shl 16) and 0xff0000) or
((data[offset + index+2].toInt() shl 8) and 0xff00) or
(data[offset + index+3].toInt() and 0xff)
)
}
fun setLong(index: Int, value: Long) {
check(offset + index + 7 < offset + length) { "Index out of bounds" }
data[offset + index] = ((value shr 56) and 0xff).toByte()
data[offset + index + 1] = ((value shr 48) and 0xff).toByte()
data[offset + index + 2] = ((value shr 40) and 0xff).toByte()
data[offset + index + 3] = ((value shr 32) and 0xff).toByte()
data[offset + index + 4] = ((value shr 24) and 0xff).toByte()
data[offset + index + 5] = ((value shr 16) and 0xff).toByte()
data[offset + index + 6] = ((value shr 8) and 0xff).toByte()
data[offset + index + 7] = (value and 0xff).toByte()
}
fun getLong(index: Int): Long {
check(offset + index + 7 < offset + length) { "Index out of bounds" }
return (
(data[offset + index].toLong() shl 56) or
((data[offset + index+1].toLong() and 0xff) shl 48) or
((data[offset + index+2].toLong() and 0xff) shl 40) or
((data[offset + index+3].toLong() and 0xff) shl 32) or
((data[offset + index+4].toLong() and 0xff) shl 24) or
((data[offset + index+5].toLong() and 0xff) shl 16) or
((data[offset + index+6].toLong() and 0xff) shl 8) or
(data[offset + index+7].toLong() and 0xff)
)
}
fun setFloat(index: Int, value: Float) = setInt(index, value.toRawBits())
fun getFloat(index: Int): Float = Float.fromBits(getInt(index))
fun setDouble(index: Int, value: Double) = setLong(index, value.toRawBits())
fun getDouble(index: Int): Double = Double.fromBits(getLong(index))
fun getClob(index: Int): String {
val length = getInt(index)
check(offset + index + length < offset + this.length) { "Index out of bounds" }
val str = data.copyOfRange(offset + index + 4, offset + index + 4 + length).decodeToString()
return str
}
fun getBlob(index: Int): SlicedByteArray {
val length = getInt(index)
check(offset + index + length < offset + this.length) { "Index out of bounds" }
val str = SlicedByteArray(data, offset + index + 4, length)
return str
}
fun copyInto(dest: ByteArray, destOffset: Int, length: Int) {
data.copyInto(dest, destOffset, offset, offset + length)
}
fun iterator(): Iterator<Byte> = object : Iterator<Byte> {
var index = offset
override fun hasNext(): Boolean {
return index < offset + length
}
override fun next(): Byte {
return data[offset + index++]
}
}
fun toByteArray(): ByteArray = data.copyOfRange(offset, offset + length)
fun getString(index: Int): String {
val length = getShort(offset + index)
val str = data.decodeToString(offset + index + 2, offset + index + 2 + length.toInt())
return str
}
fun getString(start: Int, length: Int): String = data.decodeToString(offset + start, offset + start + length)
fun setString(index: Int, value: String, maxLength: Int) {
val bytes = value.encodeToByteArray()
if (bytes.size > maxLength) {
throw IllegalArgumentException("String is too long")
}
this.setShort(offset + index, bytes.size.toShort())
bytes.copyInto(data, offset + index + 2)
}
fun setClob(index: Int, value: String, maxLength: Int) {
val bytes = value.encodeToByteArray()
if (offset + index + bytes.size + 4 > maxLength) {
throw IllegalArgumentException("String is too long")
}
this.setInt(offset + index, bytes.size)
bytes.copyInto(data, offset + index + 4)
}
fun setBlob(index: Int, bytes: SlicedByteArray, maxLength: Int) {
if (bytes.size > maxLength) {
throw IllegalArgumentException("Blob is too long")
}
this.setInt(offset + index, bytes.size)
bytes.copyInto(data, offset + index + 4, maxLength - 4)
}
companion object {
fun wrap(
data: ByteArray,
offset: Int,
length: Int
offset: Int = 0,
length: Int = data.size
): SlicedByteArray {
return SlicedByteArray(data, offset,length)
}

View File

@@ -14,7 +14,7 @@ class TypedByteArrayWithByteArrayTest {
this.name = name
}
constructor(data: ByteArray): this() {
constructor(data: SlicedByteArray): this() {
this.data = MutableByteArrayHandler(buffer = data)
}
}
@@ -29,7 +29,7 @@ class TypedByteArrayWithByteArrayTest {
constructor(name: String, person: Person1): this() {
this.name = name
this.personData = person.data.buffer.data
this.personData = person.data.buffer
}
constructor(data: ByteArray): this() {
@@ -47,8 +47,40 @@ class TypedByteArrayWithByteArrayTest {
assertEquals("A Name", company.person.name)
company.person.name = "2nd Name"
//company.person.name = "2nd Name"
assertEquals("2nd Name", company.person.name)
//assertEquals("2nd Name", company.person.name)
}
class Data() : TypedByteArray(
Type("name", DataType.STRING, 10),
Type("binaryData", DataType.BLOB, 10)
) {
var name by string("name")
var binaryData by blob("binaryData")
constructor(name: String, data: SlicedByteArray): this() {
this.name = name
this.binaryData = data
}
constructor(data: ByteArray): this() {
this.data = MutableByteArrayHandler(buffer = data)
}
}
@Test
fun testBlob() {
val binaryData = ByteArray(10)
for(index in 0..<10) {
binaryData[index] = index.toByte()
}
val data = Data("A Name", SlicedByteArray.wrap(binaryData))
assertEquals(1, data.data[17])
data.binaryData[0] = 100
assertEquals(100, data.data[16])
}
}

View File

@@ -0,0 +1,52 @@
package nl.astraeus.tba
import kotlin.test.Test
class TestCachedString {
class PersonCached : TypedByteArray(
Type("name", DataType.STRING, 100),
) {
var name by cachedString("name")
}
class PersonNotCached : TypedByteArray(
Type("name", DataType.STRING, 100),
) {
var name by string("name")
}
@Test
fun testCachedString() {
val cachedPerson = PersonCached()
val notCachedPerson = PersonNotCached()
cachedPerson.name = "A Longer Longer Longer Longer Longer Test"
notCachedPerson.name = "A Longer Longer Longer Longer Longer Test"
var total = 0
repeat(1000000) {
total += cachedPerson.name.hashCode()
total += notCachedPerson.name.hashCode()
}
println("Total warmup: $total")
var start1 = System.nanoTime()
var total1 = 0
repeat(1000000) {
total1 += cachedPerson.name.hashCode()
}
val stop1 = System.nanoTime()
var start2 = System.nanoTime()
var total2 = 0
repeat(1000000) {
total2 += notCachedPerson.name.hashCode()
}
val stop2 = System.nanoTime()
println("Total1 ($total1) time: ${(stop1 - start1) / 1000000f}ms")
println("Total2 ($total2) time: ${(stop2 - start2) / 1000000f}ms")
}
}