From 48da32330ee508921b1e6b962b3a0931d5af4921 Mon Sep 17 00:00:00 2001 From: Valentyn Sobol Date: Tue, 27 Aug 2024 16:14:59 +0300 Subject: [PATCH] ERS optimizations [jacodb-core] Optimize CompactPersistentLongSet [jacodb-core] Fix method names: getPropertyNames(), getBlobNames(), getLinkNames() In SqlErsTransaction, TODO stub implementations added. [jacodb-core] Optimize storing tiny blobs in RAMPersistentDataContainer [jacodb-core] Correct code in Iinc5 test class [jacodb-core] In SafeSubstitution, ignore substitution problems for unknown classes [jacodb-core] Add test for try-catch blocks inside Kotlin inline lambda --- .../jacodb/api/jvm/storage/ers/Transaction.kt | 4 +- .../ers/decorators/AbstractDecorators.kt | 4 +- .../impl/storage/ers/kv/KVErsTransaction.kt | 4 +- .../jacodb/impl/storage/ers/ram/BlobsCache.kt | 30 ++++++++++++++ .../ers/ram/CompactPersistentLongSet.kt | 23 ++++------- .../ers/ram/RAMPersistentDataContainer.kt | 2 +- .../impl/storage/ers/ram/RAMTransaction.kt | 4 +- .../impl/storage/ers/sql/SqlErsTransaction.kt | 12 ++++++ .../impl/types/substition/JcSubstitutors.kt | 39 +++++++++++++----- .../kotlin/org/jacodb/testing/cfg/IincTest.kt | 4 ++ .../ers/CompactPersistentLongSetTest.kt | 40 +++++++++++++++++++ .../kotlin/org/jacodb/testing/cfg/Iinc.kt | 19 +++++++++ 12 files changed, 149 insertions(+), 36 deletions(-) create mode 100644 jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/BlobsCache.kt create mode 100644 jacodb-core/src/test/kotlin/org/jacodb/testing/storage/ers/CompactPersistentLongSetTest.kt diff --git a/jacodb-api-jvm/src/main/kotlin/org/jacodb/api/jvm/storage/ers/Transaction.kt b/jacodb-api-jvm/src/main/kotlin/org/jacodb/api/jvm/storage/ers/Transaction.kt index 0a1a70680..4313a4d19 100644 --- a/jacodb-api-jvm/src/main/kotlin/org/jacodb/api/jvm/storage/ers/Transaction.kt +++ b/jacodb-api-jvm/src/main/kotlin/org/jacodb/api/jvm/storage/ers/Transaction.kt @@ -53,12 +53,12 @@ interface Transaction : Closeable { /** * Returns set of blob names ever being set to an entity of specified `type`. */ - fun getBlobNamesNames(type: String): Set = emptySet() + fun getBlobNames(type: String): Set = emptySet() /** * Returns set of link names ever being set to an entity of specified `type`. */ - fun getLinkNamesNames(type: String): Set = emptySet() + fun getLinkNames(type: String): Set = emptySet() fun all(type: String): EntityIterable diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/decorators/AbstractDecorators.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/decorators/AbstractDecorators.kt index 5a926cf2c..e7799552f 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/decorators/AbstractDecorators.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/decorators/AbstractDecorators.kt @@ -36,8 +36,8 @@ abstract class AbstractTransactionDecorator : Transaction { override fun deleteEntity(id: EntityId) = delegate.deleteEntity(id) override fun getTypeId(type: String): Int = delegate.getTypeId(type) override fun getPropertyNames(type: String): Set = delegate.getPropertyNames(type) - override fun getBlobNamesNames(type: String): Set = delegate.getBlobNamesNames(type) - override fun getLinkNamesNames(type: String): Set = delegate.getLinkNamesNames(type) + override fun getBlobNames(type: String): Set = delegate.getBlobNames(type) + override fun getLinkNames(type: String): Set = delegate.getLinkNames(type) override fun all(type: String): EntityIterable = delegate.all(type) override fun find(type: String, propertyName: String, value: T): EntityIterable = delegate.find(type, propertyName, value) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/kv/KVErsTransaction.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/kv/KVErsTransaction.kt index 07f793017..ef63cd069 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/kv/KVErsTransaction.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/kv/KVErsTransaction.kt @@ -89,11 +89,11 @@ class KVErsTransaction( ers.getPropNameFromMapName(it) } - override fun getBlobNamesNames(type: String): Set = getAttributeName(type) { + override fun getBlobNames(type: String): Set = getAttributeName(type) { ers.getBlobNameFromMapName(it) } - override fun getLinkNamesNames(type: String): Set = getAttributeName(type) { + override fun getLinkNames(type: String): Set = getAttributeName(type) { ers.getLinkNameFromMapName(it) } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/BlobsCache.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/BlobsCache.kt new file mode 100644 index 000000000..f23b1e7ac --- /dev/null +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/BlobsCache.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.impl.storage.ers.ram + + +fun ByteArray.probablyCached(): ByteArray { + return if (size == 1) { + theCache[this[0].toInt() and 0xff] + } else { + this + } +} + +private val theCache = Array(256) { i -> + byteArrayOf(i.toByte()) +} \ No newline at end of file diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/CompactPersistentLongSet.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/CompactPersistentLongSet.kt index 422d4cdd1..fc0a06557 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/CompactPersistentLongSet.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/CompactPersistentLongSet.kt @@ -60,18 +60,16 @@ internal class CompactPersistentLongSet(private val value: Any? = null) : Collec if (value == element) { this } else { - CompactPersistentLongSet(PackedPersistentLongSet().addAll(listOf(value, element))) + CompactPersistentLongSet(PackedPersistentLongSet().addAll(listOf(value, element.interned))) } - is PackedPersistentLongSet -> { - val newValue = value.add(element) + val newValue = value.add(element.interned) if (newValue === value) { this } else { - CompactPersistentLongSet(value.add(element)) + CompactPersistentLongSet(newValue) } } - else -> throw illegalStateException() } } @@ -85,16 +83,9 @@ internal class CompactPersistentLongSet(private val value: Any? = null) : Collec if (newValue === value) { this } else { - newValue.run { - if (size == 1) { - CompactPersistentLongSet(first().interned) - } else { - CompactPersistentLongSet(newValue) - } - } + CompactPersistentLongSet(if (newValue.size == 1) newValue.first() else newValue) } } - else -> throw illegalStateException() } } @@ -103,13 +94,13 @@ internal class CompactPersistentLongSet(private val value: Any? = null) : Collec IllegalStateException("CompactPersistentLongSet.value can only be Long or PersistentLongSet") } -private val Any?.interned: Any? +private val Long.interned: Long get() = - if (this is Long && this >= 0 && this < LongInterner.boxedLongs.size) LongInterner.boxedLongs[this.toInt()] + if (this in 0 until LongInterner.boxedLongs.size) LongInterner.boxedLongs[this.toInt()] else this // TODO: remove this interner if specialized persistent collections would be used object LongInterner { - val boxedLongs = Array(200000) { it.toLong() } + val boxedLongs = Array(200000) { it.toLong() } } \ No newline at end of file diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/RAMPersistentDataContainer.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/RAMPersistentDataContainer.kt index b78a1395a..cc7622940 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/RAMPersistentDataContainer.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/RAMPersistentDataContainer.kt @@ -235,7 +235,7 @@ internal class RAMPersistentDataContainer( } else { val blobs = blobs[blobsKey] ?: TransactionalPersistentLongMap() blobs.withMutableBlobs(blobsKey) { - put(id.instanceId, value) + put(id.instanceId, value.probablyCached()) } } } diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/RAMTransaction.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/RAMTransaction.kt index 39791c2c1..f7ae9c930 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/RAMTransaction.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/ram/RAMTransaction.kt @@ -51,9 +51,9 @@ internal class RAMTransaction(override val ers: RAMEntityRelationshipStorage) : override fun getPropertyNames(type: String): Set = dataContainerChecked.getPropertyNames(type) - override fun getBlobNamesNames(type: String): Set = dataContainerChecked.getBlobNames(type) + override fun getBlobNames(type: String): Set = dataContainerChecked.getBlobNames(type) - override fun getLinkNamesNames(type: String): Set = dataContainerChecked.getLinkNames(type) + override fun getLinkNames(type: String): Set = dataContainerChecked.getLinkNames(type) override fun all(type: String): EntityIterable = dataContainerChecked.all(this, type) diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/sql/SqlErsTransaction.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/sql/SqlErsTransaction.kt index d197b6645..6d56f737e 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/sql/SqlErsTransaction.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/storage/ers/sql/SqlErsTransaction.kt @@ -75,6 +75,18 @@ class SqlErsTransactionImpl( return getTypeIdOrNull(type) ?: return -1 } + override fun getPropertyNames(type: String): Set { + TODO("Not yet implemented") + } + + override fun getBlobNames(type: String): Set { + TODO("Not yet implemented") + } + + override fun getLinkNames(type: String): Set { + TODO("Not yet implemented") + } + override fun all(type: String): EntityIterable { val typeId = getTypeIdOrNull(type) ?: return EntityIterable.EMPTY val entityTable = getEntityTableByTypeIdOrNull(typeId) ?: return EntityIterable.EMPTY diff --git a/jacodb-core/src/main/kotlin/org/jacodb/impl/types/substition/JcSubstitutors.kt b/jacodb-core/src/main/kotlin/org/jacodb/impl/types/substition/JcSubstitutors.kt index 74f9cbdd0..7935ddd5a 100644 --- a/jacodb-core/src/main/kotlin/org/jacodb/impl/types/substition/JcSubstitutors.kt +++ b/jacodb-core/src/main/kotlin/org/jacodb/impl/types/substition/JcSubstitutors.kt @@ -16,8 +16,13 @@ package org.jacodb.impl.types.substition -import org.jacodb.api.jvm.* +import org.jacodb.api.jvm.JcClassOrInterface +import org.jacodb.api.jvm.JcGenericsSubstitutionFeature +import org.jacodb.api.jvm.JcSubstitutor +import org.jacodb.api.jvm.JvmType +import org.jacodb.api.jvm.JvmTypeParameterDeclaration import org.jacodb.impl.cfg.util.OBJECT_CLASS +import org.jacodb.impl.features.classpaths.JcUnknownClass import org.jacodb.impl.types.signature.JvmClassRefType import org.jacodb.impl.types.typeParameters @@ -40,27 +45,39 @@ object SafeSubstitution : JcGenericsSubstitutionFeature { outer: JcSubstitutor? ): JcSubstitutor { val params = clazz.typeParameters - require(params.size == parameters.size) { - "Incorrect parameters specified for class ${clazz.name}: expected ${params.size} found ${parameters.size}" + return if (clazz is JcUnknownClass) { + ignoreProblemsAndSubstitute(params, parameters, outer) + } else { + require(params.size == parameters.size) { + "Incorrect parameters specified for class ${clazz.name}: expected ${params.size} found ${parameters.size}" + } + params.substitute(parameters, outer) } - return params.substitute(parameters, outer) } } object IgnoreSubstitutionProblems : JcGenericsSubstitutionFeature { - private val jvmObjectType = JvmClassRefType(OBJECT_CLASS, true, emptyList()) - override fun substitute( clazz: JcClassOrInterface, parameters: List, outer: JcSubstitutor? ): JcSubstitutor { val params = clazz.typeParameters - if (params.size == parameters.size) { - return params.substitute(parameters, outer) - } - val substitution = params.associateWith { it.bounds?.first() ?: jvmObjectType } - return (outer ?: JcSubstitutorImpl.empty).newScope(substitution) + return ignoreProblemsAndSubstitute(params, parameters, outer) + } +} + +private val jvmObjectType = JvmClassRefType(OBJECT_CLASS, true, emptyList()) + +private fun ignoreProblemsAndSubstitute( + params: List, + parameters: List, + outer: JcSubstitutor? +): JcSubstitutor { + if (params.size == parameters.size) { + return params.substitute(parameters, outer) } + val substitution = params.associateWith { it.bounds?.first() ?: jvmObjectType } + return (outer ?: JcSubstitutorImpl.empty).newScope(substitution) } diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IincTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IincTest.kt index 848520461..fff06fe2c 100644 --- a/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IincTest.kt +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/cfg/IincTest.kt @@ -19,6 +19,7 @@ package org.jacodb.testing.cfg import org.jacodb.api.jvm.ext.findClass import org.junit.jupiter.api.Assertions.assertArrayEquals import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test class IincTest : BaseInstructionsTest() { @@ -113,4 +114,7 @@ class IincTest : BaseInstructionsTest() { @Test fun `kotlin iinc4`() = runTest(Iinc4::class.java.name) + @Test + fun `kotlin iinc5`() = runTest(Iinc5::class.java.name) + } diff --git a/jacodb-core/src/test/kotlin/org/jacodb/testing/storage/ers/CompactPersistentLongSetTest.kt b/jacodb-core/src/test/kotlin/org/jacodb/testing/storage/ers/CompactPersistentLongSetTest.kt new file mode 100644 index 000000000..2b2487d13 --- /dev/null +++ b/jacodb-core/src/test/kotlin/org/jacodb/testing/storage/ers/CompactPersistentLongSetTest.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2022 UnitTestBot contributors (utbot.org) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jacodb.testing.storage.ers + +import org.jacodb.impl.storage.ers.ram.CompactPersistentLongSet +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class CompactPersistentLongSetTest { + + @Test + fun `add remove`() { + var set = CompactPersistentLongSet() + set = set.add(1L) + set = set.add(2L) + set = set.add(3L) + assertEquals(set.size, 3) + assertEquals(setOf(1L, 2L, 3L), set.toSet()) + set = set.remove(2L) + assertEquals(set.size, 2) + assertEquals(setOf(1L, 3L), set.toSet()) + set = set.remove(3L) + assertEquals(set.size, 1) + assertEquals(setOf(1L), set.toSet()) + } +} \ No newline at end of file diff --git a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/cfg/Iinc.kt b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/cfg/Iinc.kt index d6def2a13..9569211f7 100644 --- a/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/cfg/Iinc.kt +++ b/jacodb-core/src/testFixtures/kotlin/org/jacodb/testing/cfg/Iinc.kt @@ -168,3 +168,22 @@ class Iinc4 { } } + +class Iinc5 { + + fun box(): String { + val result = intArrayOf(0) + var i = 0 + + repeat(10) { + try { + result[i++]++ + } catch (_: ArrayIndexOutOfBoundsException) { + result[0] += 2 + i = 0 + } + } + + return if (result[0] == 15) "OK" else "Catch failed: ${result[0]}" + } +}