Skip to content

Commit

Permalink
Native support (#11)
Browse files Browse the repository at this point in the history
Support for macos, ios, linux targets across x86 and ARM
  • Loading branch information
CharlieTap authored Dec 17, 2023
1 parent ad2f266 commit cbd73b7
Show file tree
Hide file tree
Showing 41 changed files with 876 additions and 537 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# cachemap

![badge][badge-android]
![badge][badge-jvm]
![badge][badge-ios]
![badge][badge-linux]
![badge][badge-mac]

---

Expand Down Expand Up @@ -146,4 +150,8 @@ This project is dual-licensed under both the MIT and Apache 2.0 licenses. You ca
- For details on the MIT license, please see the [LICENSE-MIT](LICENSE-MIT) file.
- For details on the Apache 2.0 license, please see the [LICENSE-APACHE](LICENSE-APACHE) file.

[badge-android]: http://img.shields.io/badge/-android-6EDB8D.svg?style=flat
[badge-jvm]: http://img.shields.io/badge/-jvm-DB413D.svg?style=flat
[badge-linux]: http://img.shields.io/badge/-linux-2D3F6C.svg?style=flat
[badge-ios]: http://img.shields.io/badge/-ios-CDCDCD.svg?style=flat
[badge-mac]: http://img.shields.io/badge/-macos-111111.svg?style=flat
3 changes: 3 additions & 0 deletions benchmark/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ plugins {

allOpen {
annotation("org.openjdk.jmh.annotations.State")
annotation("kotlinx.benchmark.State")
}

benchmark {
targets {
register("jvm")
// register("macosArm64")
}
}

kotlin {

jvm()
macosArm64()

jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(libs.versions.java.compiler.version.get().toInt()))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.charlietap.cachemap.benchmark

// Todo enable threading
class CacheMapMultiThreadedBenchmark : CacheMapSingleThreadBenchmark()
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.github.charlietap.cachemap.benchmark

import io.github.charlietap.cachemap.cacheMapOf
import kotlinx.benchmark.Benchmark
import kotlinx.benchmark.BenchmarkMode
import kotlinx.benchmark.BenchmarkTimeUnit
import kotlinx.benchmark.Blackhole
import kotlinx.benchmark.Measurement
import kotlinx.benchmark.Mode
import kotlinx.benchmark.OutputTimeUnit
import kotlinx.benchmark.Scope
import kotlinx.benchmark.Setup
import kotlinx.benchmark.State
import kotlinx.benchmark.TearDown
import kotlinx.benchmark.Warmup

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime, Mode.Throughput)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = BenchmarkConfig.WARMUP_ITERATIONS)
@Measurement(iterations = BenchmarkConfig.MEASUREMENT_ITERATIONS, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS)
class CacheMapSingleThreadBenchmark {

private val cacheMap = cacheMapOf<String, String>()

@Setup()
fun setup() {
for (i in 1..1000) {
cacheMap.put("key$i", "value$i")
}
}

@Benchmark
fun put(blackhole: Blackhole) {
val result = cacheMap.put("Hello", "World")
blackhole.consume(result)
}

@Benchmark
fun overwrite(blackhole: Blackhole) {
val result = cacheMap.put("key1", "value2")
blackhole.consume(result)
}

@Benchmark
fun putAll(blackhole: Blackhole) {
val anotherMap = mapOf("Hello" to "World", "SecondKey" to "SecondValue")
val result = cacheMap.putAll(anotherMap)
blackhole.consume(result)
}

@Benchmark
fun get(blackhole: Blackhole) {
val result: String? = cacheMap["key1"]
blackhole.consume(result)
}

@Benchmark
fun getMiss(blackhole: Blackhole) {
val result: String? = cacheMap["Hello"]
blackhole.consume(result)
}

@Benchmark
fun remove(blackhole: Blackhole) {
val result = cacheMap.remove("key1")
blackhole.consume(result)
}

@Benchmark
fun stressTest(blackhole: Blackhole) {
for (i in 1..1000) {
val putResult = cacheMap.put("newKey$i", "newValue$i")
blackhole.consume(putResult)

val getResult: String? = cacheMap["key$i"]
blackhole.consume(getResult)

val removeResult = cacheMap.remove("newKey$i")
blackhole.consume(removeResult)
}
}

@TearDown()
fun tearDown() {
cacheMap.clear()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.github.charlietap.cachemap.benchmark

// todo add threads
class RWHashMapMultiThreadedBenchmark : RWHashMapSingleThreadedBenchmark()
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package io.github.charlietap.cachemap.benchmark

import kotlinx.atomicfu.locks.ReentrantLock
import kotlinx.atomicfu.locks.withLock
import kotlinx.benchmark.Benchmark
import kotlinx.benchmark.BenchmarkMode
import kotlinx.benchmark.BenchmarkTimeUnit
import kotlinx.benchmark.Blackhole
import kotlinx.benchmark.Measurement
import kotlinx.benchmark.Mode
import kotlinx.benchmark.OutputTimeUnit
import kotlinx.benchmark.Scope
import kotlinx.benchmark.Setup
import kotlinx.benchmark.State
import kotlinx.benchmark.TearDown
import kotlinx.benchmark.Warmup

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime, Mode.Throughput)
@OutputTimeUnit(BenchmarkTimeUnit.NANOSECONDS)
@Warmup(iterations = BenchmarkConfig.WARMUP_ITERATIONS)
@Measurement(iterations = BenchmarkConfig.MEASUREMENT_ITERATIONS, time = 1, timeUnit = BenchmarkTimeUnit.SECONDS)
class RWHashMapSingleThreadedBenchmark {

private val map = HashMap<String, String>()
private val lock = ReentrantLock()

@Setup()
fun setup() {
for (i in 1..1000) {
map["key$i"] = "value$i"
}
}

@Benchmark
fun put(blackhole: Blackhole) {
val result = lock.withLock {
map.put("Hello", "World")
}
blackhole.consume(result)
}

@Benchmark
fun overwrite(blackhole: Blackhole) {
val result = lock.withLock {
map.put("key1", "value2")
}
blackhole.consume(result)
}

@Benchmark
fun putAll(blackhole: Blackhole) {
val anotherMap = mapOf("Hello" to "World", "SecondKey" to "SecondValue")
lock.withLock {
map.putAll(anotherMap)
}
blackhole.consume(anotherMap)
}

@Benchmark
fun get(blackhole: Blackhole) {
val result: String? = lock.withLock {
map["key1"]
}
blackhole.consume(result)
}

@Benchmark
fun getMiss(blackhole: Blackhole) {
val result: String? = lock.withLock {
map["Hello"]
}
blackhole.consume(result)
}

@Benchmark
fun remove(blackhole: Blackhole) {
val result = lock.withLock {
map.remove("key1")
}
blackhole.consume(result)
}

@Benchmark
fun stressTest(blackhole: Blackhole) {
for (i in 1..1000) {
val putResult = lock.withLock {
map.put("newKey$i", "newValue$i")
}
blackhole.consume(putResult)

val getResult: String? = lock.withLock {
map["key$i"]
}
blackhole.consume(getResult)

val removeResult = lock.withLock {
map.remove("newKey$i")
}
blackhole.consume(removeResult)
}
}

@TearDown()
fun tearDown() {
map.clear()
}
}
96 changes: 5 additions & 91 deletions cachemap-suspend/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,12 @@ plugins {
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.atomic.fu)
alias(libs.plugins.kotlinter)
alias(libs.plugins.dokka)
id("maven-publish")
id("signing")
id("kmp-conventions")
id("publishing-conventions")
}

kotlin {

jvm()

jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(libs.versions.java.compiler.version.get().toInt()))
vendor.set(JvmVendorSpec.matching(libs.versions.java.vendor.get()))
}

targets.configureEach {
compilations.configureEach {
kotlinOptions {

}
}
}

sourceSets {

commonMain {
Expand All @@ -51,79 +35,9 @@ kotlin {
}
}

val dokkaHtml by tasks.getting(org.jetbrains.dokka.gradle.DokkaTask::class)

val javadocJar: TaskProvider<Jar> by tasks.registering(Jar::class) {
dependsOn(dokkaHtml)
archiveClassifier.set("javadoc")
from(dokkaHtml.outputDirectory)
}

tasks.withType<AbstractPublishToMaven>().configureEach {
val signingTasks = tasks.withType<Sign>()
mustRunAfter(signingTasks)
}

tasks.withType<DokkaTask>().configureEach {
notCompatibleWithConfigurationCache("https://github.com/Kotlin/dokka/issues/2231")
}

group = "io.github.charlietap"
version = libs.versions.version.name.get()

publishing {

val manualFileRepo = uri("file://${rootProject.layout.buildDirectory.get()}/manual")

repositories {
maven {
name = "manual"
url = manualFileRepo
}
}

publications.withType<MavenPublication>().configureEach {

artifact(javadocJar)

pom {
name.set(project.name)
description.set("A read optimised suspending concurrent map for Kotlin Multiplatform")
url.set("https://github.com/CharlieTap/cachemap")
licenses {
license {
name.set("Apache-2.0")
url.set("https://www.apache.org/licenses/LICENSE-2.0")
}
license {
name.set("MIT")
url.set("https://opensource.org/licenses/MIT")
}
}
developers {
developer {
id.set("CharlieTap")
name.set("Charlie Tapping")
}
}
scm {
connection.set("scm:git:https://github.com/CharlieTap/cachemap.git")
developerConnection.set("scm:git:ssh://github.com/CharlieTap/cachemap.git")
url.set("https://github.com/CharlieTap/cachemap")
}
}
}
}

signing {
val signingKey: String? by project
val signingPassword: String? by project

if(signingKey != null) {
useInMemoryPgpKeys(signingKey, signingPassword)
}

sign(project.extensions.getByType<PublishingExtension>().publications)
configure<PublishingConventionsExtension> {
name = "cachemap-suspend"
description = "A read optimised suspending concurrent map for Kotlin Multiplatform"
}

tasks.withType<KotlinCompile>().configureEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ internal class InternalSuspendCacheMap<K, V>(

override suspend fun remove(key: K, value: V): Boolean {
return inner.mutate { map ->
map.remove(key, value)
val mapValue = map[key]
if (value == mapValue) {
map.remove(key)
true
} else {
false
}
}
}

Expand All @@ -87,8 +93,10 @@ internal class InternalSuspendCacheMap<K, V>(
val constructor = {
if (capacity != null) {
HashMap<K, V>(capacity)
} else {
} else if (population != null) {
HashMap<K, V>(population)
} else {
HashMap<K, V>()
}
}
if (readerParallelism != null) {
Expand Down
Loading

0 comments on commit cbd73b7

Please sign in to comment.