Skip to content
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
16 changes: 15 additions & 1 deletion examples/map/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,26 @@ java -jar build/libs/*shadowjar*.jar
* Same as onheap but uses NativeMemoryMap with off-heap storage.
* Uses the ConcurrentHashMap backend for NativeMemoryMap, which does not support eviction.
* Total of 10GB of data in off-heap memory.
* At end of test after full GC uses ~130MB of Java heap space.

## offheap-eviction example

* Same as offheap but uses the Caffeine backend with maximumSize of 10,000 entries.
* Expect 10,000 map entries at end of run with 10,000 total evictions.
* Total of 5GB of data in off-heap memory.
* At end of test after full GC uses ~90MB of Java heap space.

## offheap-eviction-operationcounters example

* Same as offheap-eviction but enables operation counters and logs them at the end of the demo.
* Same as offheap-eviction but enables operation counters and logs them at the end of the demo.
* Total of 5GB of data in off-heap memory.
* At end of test after full GC uses ~90MB of Java heap space.

## offheap-flatbuffers example

* Example using [Google FlatBuffers](https://google.github.io/flatbuffers/) for serialization.
* Put 20,000 DemoCacheObject instances into a NativeMemoryMap. Each DemoCacheObject contains a list of 2,000
DemoCacheObjectListEntry objects.
* Total of ~11GB of data in off-heap memory.
* DemoCacheObjectSerializer does conversions between DemoCacheObject and FlatBuffer objects.
* At end of test after full GC uses ~130MB of Java heap space.
30 changes: 30 additions & 0 deletions examples/map/offheap-flatbuffers/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar

plugins {
kotlin("jvm")
id("com.github.johnrengelman.shadow") version "7.1.2"
}

repositories {
mavenCentral()
}

dependencies {
implementation(project(":examples:map:utils"))
implementation("com.google.flatbuffers:flatbuffers-java:2.0.3")
}

tasks {
named<ShadowJar>("shadowJar") {
archiveBaseName.set("offheap-flatbuffers-shadowjar")
manifest {
attributes(mapOf("Main-Class" to "com.target.nativememoryallocator.examples.map.offheap.flatbuffers.OffHeapFlatBuffersKt"))
}
}
}

tasks {
build {
dependsOn(shadowJar)
}
}
10 changes: 10 additions & 0 deletions examples/map/offheap-flatbuffers/generate_fbs_code.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

# Script to invoke the FlatBuffers compiler flatc to generate Kotlin FlatBuffer classes.
# You must have flatc installed locally and in your PATH before running this script.

BASE_DIRECTORY=$(dirname $0)

rm -fr $BASE_DIRECTORY/src/main/kotlin/com/target/nativememoryallocator/examples/map/offheap/flatbuffers/generated

flatc --kotlin -o $BASE_DIRECTORY/src/main/kotlin $BASE_DIRECTORY/src/main/flatbuffers/DemoSchema.fbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated;

table FlatBufferDemoCacheObjectListEntry {
id: int;
boolean_field: bool;
string_field: string;
}

table FlatBufferDemoCacheObject {
id: int;
entry_list: [FlatBufferDemoCacheObjectListEntry];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.target.nativememoryallocator.examples.map.offheap.flatbuffers

import com.target.nativememoryallocator.allocator.NativeMemoryAllocatorBuilder
import com.target.nativememoryallocator.examples.map.offheap.flatbuffers.model.DemoCacheObject
import com.target.nativememoryallocator.examples.map.offheap.flatbuffers.model.DemoCacheObjectListEntry
import com.target.nativememoryallocator.examples.map.offheap.flatbuffers.serializer.DemoCacheObjectSerializer
import com.target.nativememoryallocator.examples.map.utils.buildRandomString
import com.target.nativememoryallocator.map.NativeMemoryMapBackend
import com.target.nativememoryallocator.map.NativeMemoryMapBuilder
import kotlinx.coroutines.*
import mu.KotlinLogging
import java.util.concurrent.ThreadLocalRandom
import kotlin.random.Random

private val logger = KotlinLogging.logger {}


/**
* Demo application that puts 20,000 [DemoCacheObject] instances into a [NativeMemoryMap].
*
* This demo uses the ConcurrentHashMap backend for [NativeMemoryMap].
*
* Each [DemoCacheObject] instance contains a list of 2,000 [DemoCacheObjectListEntry]s.
*
* This is a total of ~11 GB of data in off-heap memory.
*/
private class OffHeapFlatBuffers {

private val numMapEntries = 20_000

private val numEntriesPerObject = 2_000

private val randomIndex = Random.nextInt(0, numMapEntries)

private var randomPutValue: DemoCacheObject? = null

private val nativeMemoryAllocator = NativeMemoryAllocatorBuilder(
pageSizeBytes = 4_096, // 4 KB
nativeMemorySizeBytes = (20L * 1_024L * 1_024L * 1_024L), // 20 GB
).build()

private val nativeMemoryMap = NativeMemoryMapBuilder<Int, DemoCacheObject>(
valueSerializer = DemoCacheObjectSerializer(),
nativeMemoryAllocator = nativeMemoryAllocator,
backend = NativeMemoryMapBackend.CONCURRENT_HASH_MAP,
).build()

private fun buildDemoCacheObject(i: Int) =
DemoCacheObject(
id = i,
entryList = (0 until numEntriesPerObject).map { j ->
DemoCacheObjectListEntry(
id = j,
booleanField = ThreadLocalRandom.current().nextBoolean(),
stringField = buildRandomString(length = 250),
)
}
)

private fun putValueIntoMap(i: Int) {
if ((i % 100) == 0) {
logger.info { "put i = $i" }
}
val demoCacheObject = buildDemoCacheObject(i = i)
if (i == randomIndex) {
logger.info { "put randomIndex = $randomIndex demoCacheObject.entryList.size = ${demoCacheObject.entryList.size}" }
logger.info {
"substring = ${demoCacheObject.entryList[0].stringField.substring(0, 20)}"
}
randomPutValue = demoCacheObject
}
nativeMemoryMap.put(
key = i,
value = demoCacheObject,
)
}

suspend fun run() {
logger.info { "begin run randomIndex = $randomIndex" }

coroutineScope {
(0 until numMapEntries).forEach { i ->
launch {
putValueIntoMap(i = i)
}
}
}

logger.info { "nativeMemoryMap.size = ${nativeMemoryMap.size}" }
logger.info { "nativeMemoryAllocator.nativeMemoryAllocatorMetadata = ${nativeMemoryAllocator.nativeMemoryAllocatorMetadata}" }

val randomIndexValue = nativeMemoryMap.get(key = randomIndex)
randomIndexValue?.let {
logger.info { "get randomIndex = $randomIndex" }
logger.info { "randomIndexValue.entryList.size = ${it.entryList.size}" }
logger.info { "substring = ${it.entryList[0].stringField.substring(0, 20)}" }
logger.info { "randomIndexValue == randomPutValue = ${it == randomPutValue}" }
}

while (true) {
delay(1_000)
}
}
}

suspend fun main() {
withContext(Dispatchers.Default) {
OffHeapFlatBuffers().run()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// automatically generated by the FlatBuffers compiler, do not modify

package com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated

import java.nio.*
import kotlin.math.sign
import com.google.flatbuffers.*

@Suppress("unused")
class FlatBufferDemoCacheObject : Table() {

fun __init(_i: Int, _bb: ByteBuffer) {
__reset(_i, _bb)
}
fun __assign(_i: Int, _bb: ByteBuffer) : FlatBufferDemoCacheObject {
__init(_i, _bb)
return this
}
val id : Int
get() {
val o = __offset(4)
return if(o != 0) bb.getInt(o + bb_pos) else 0
}
fun entryList(j: Int) : com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated.FlatBufferDemoCacheObjectListEntry? = entryList(com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated.FlatBufferDemoCacheObjectListEntry(), j)
fun entryList(obj: com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated.FlatBufferDemoCacheObjectListEntry, j: Int) : com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated.FlatBufferDemoCacheObjectListEntry? {
val o = __offset(6)
return if (o != 0) {
obj.__assign(__indirect(__vector(o) + j * 4), bb)
} else {
null
}
}
val entryListLength : Int
get() {
val o = __offset(6); return if (o != 0) __vector_len(o) else 0
}
companion object {
fun validateVersion() = Constants.FLATBUFFERS_2_0_0()
fun getRootAsFlatBufferDemoCacheObject(_bb: ByteBuffer): FlatBufferDemoCacheObject = getRootAsFlatBufferDemoCacheObject(_bb, FlatBufferDemoCacheObject())
fun getRootAsFlatBufferDemoCacheObject(_bb: ByteBuffer, obj: FlatBufferDemoCacheObject): FlatBufferDemoCacheObject {
_bb.order(ByteOrder.LITTLE_ENDIAN)
return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb))
}
fun createFlatBufferDemoCacheObject(builder: FlatBufferBuilder, id: Int, entryListOffset: Int) : Int {
builder.startTable(2)
addEntryList(builder, entryListOffset)
addId(builder, id)
return endFlatBufferDemoCacheObject(builder)
}
fun startFlatBufferDemoCacheObject(builder: FlatBufferBuilder) = builder.startTable(2)
fun addId(builder: FlatBufferBuilder, id: Int) = builder.addInt(0, id, 0)
fun addEntryList(builder: FlatBufferBuilder, entryList: Int) = builder.addOffset(1, entryList, 0)
fun createEntryListVector(builder: FlatBufferBuilder, data: IntArray) : Int {
builder.startVector(4, data.size, 4)
for (i in data.size - 1 downTo 0) {
builder.addOffset(data[i])
}
return builder.endVector()
}
fun startEntryListVector(builder: FlatBufferBuilder, numElems: Int) = builder.startVector(4, numElems, 4)
fun endFlatBufferDemoCacheObject(builder: FlatBufferBuilder) : Int {
val o = builder.endTable()
return o
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// automatically generated by the FlatBuffers compiler, do not modify

package com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated

import java.nio.*
import kotlin.math.sign
import com.google.flatbuffers.*

@Suppress("unused")
class FlatBufferDemoCacheObjectListEntry : Table() {

fun __init(_i: Int, _bb: ByteBuffer) {
__reset(_i, _bb)
}
fun __assign(_i: Int, _bb: ByteBuffer) : FlatBufferDemoCacheObjectListEntry {
__init(_i, _bb)
return this
}
val id : Int
get() {
val o = __offset(4)
return if(o != 0) bb.getInt(o + bb_pos) else 0
}
val booleanField : Boolean
get() {
val o = __offset(6)
return if(o != 0) 0.toByte() != bb.get(o + bb_pos) else false
}
val stringField : String?
get() {
val o = __offset(8)
return if (o != 0) __string(o + bb_pos) else null
}
val stringFieldAsByteBuffer : ByteBuffer get() = __vector_as_bytebuffer(8, 1)
fun stringFieldInByteBuffer(_bb: ByteBuffer) : ByteBuffer = __vector_in_bytebuffer(_bb, 8, 1)
companion object {
fun validateVersion() = Constants.FLATBUFFERS_2_0_0()
fun getRootAsFlatBufferDemoCacheObjectListEntry(_bb: ByteBuffer): FlatBufferDemoCacheObjectListEntry = getRootAsFlatBufferDemoCacheObjectListEntry(_bb, FlatBufferDemoCacheObjectListEntry())
fun getRootAsFlatBufferDemoCacheObjectListEntry(_bb: ByteBuffer, obj: FlatBufferDemoCacheObjectListEntry): FlatBufferDemoCacheObjectListEntry {
_bb.order(ByteOrder.LITTLE_ENDIAN)
return (obj.__assign(_bb.getInt(_bb.position()) + _bb.position(), _bb))
}
fun createFlatBufferDemoCacheObjectListEntry(builder: FlatBufferBuilder, id: Int, booleanField: Boolean, stringFieldOffset: Int) : Int {
builder.startTable(3)
addStringField(builder, stringFieldOffset)
addId(builder, id)
addBooleanField(builder, booleanField)
return endFlatBufferDemoCacheObjectListEntry(builder)
}
fun startFlatBufferDemoCacheObjectListEntry(builder: FlatBufferBuilder) = builder.startTable(3)
fun addId(builder: FlatBufferBuilder, id: Int) = builder.addInt(0, id, 0)
fun addBooleanField(builder: FlatBufferBuilder, booleanField: Boolean) = builder.addBoolean(1, booleanField, false)
fun addStringField(builder: FlatBufferBuilder, stringField: Int) = builder.addOffset(2, stringField, 0)
fun endFlatBufferDemoCacheObjectListEntry(builder: FlatBufferBuilder) : Int {
val o = builder.endTable()
return o
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.target.nativememoryallocator.examples.map.offheap.flatbuffers.mapper

import com.google.flatbuffers.FlatBufferBuilder
import com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated.FlatBufferDemoCacheObject
import com.target.nativememoryallocator.examples.map.offheap.flatbuffers.generated.FlatBufferDemoCacheObjectListEntry
import com.target.nativememoryallocator.examples.map.offheap.flatbuffers.model.DemoCacheObject
import com.target.nativememoryallocator.examples.map.offheap.flatbuffers.model.DemoCacheObjectListEntry
import java.nio.ByteBuffer

/**
* Mapper between [DemoCacheObject] and [FlatBufferDemoCacheObject].
*/
object FlatBuffersMapper {

fun demoCacheObjectToFlatBuffer(
demoCacheObject: DemoCacheObject,
): FlatBufferDemoCacheObject {
val fbb = FlatBufferBuilder()
fbb.forceDefaults(false)

val entryListOffset = FlatBufferDemoCacheObject.createEntryListVector(
builder = fbb,
data = demoCacheObject.entryList.map { entry ->
FlatBufferDemoCacheObjectListEntry.createFlatBufferDemoCacheObjectListEntry(
builder = fbb,
id = entry.id,
booleanField = entry.booleanField,
stringFieldOffset = fbb.createString(entry.stringField),
)
}.toIntArray(),
)

val flatBufferDemoCacheObject = FlatBufferDemoCacheObject.createFlatBufferDemoCacheObject(
builder = fbb,
id = demoCacheObject.id,
entryListOffset = entryListOffset,
)

fbb.finish(flatBufferDemoCacheObject)

return FlatBufferDemoCacheObject.getRootAsFlatBufferDemoCacheObject(
ByteBuffer.wrap(
fbb.sizedByteArray(),
)
)
}

fun flatBufferToDemoCacheObject(
flatBufferDemoCacheObject: FlatBufferDemoCacheObject,
): DemoCacheObject {

val entryList = (0 until flatBufferDemoCacheObject.entryListLength).mapNotNull { i ->
val flatBufferEntry = flatBufferDemoCacheObject.entryList(i)
if (flatBufferEntry == null) {
null
} else {
DemoCacheObjectListEntry(
id = flatBufferEntry.id,
booleanField = flatBufferEntry.booleanField,
stringField = flatBufferEntry.stringField.orEmpty(),
)
}
}

return DemoCacheObject(
id = flatBufferDemoCacheObject.id,
entryList = entryList,
)
}
}
Loading