Skip to content
This repository was archived by the owner on Jan 26, 2026. It is now read-only.

Use Unsafe memory buffer instead of byte buffer - https://github.com/imprint-serde/imprint-java/issues/21#24

Merged
expanded-for-real merged 71 commits intomainfrom
custom-buffers
Jul 14, 2025
Merged

Use Unsafe memory buffer instead of byte buffer - https://github.com/imprint-serde/imprint-java/issues/21#24
expanded-for-real merged 71 commits intomainfrom
custom-buffers

Conversation

@expanded-for-real
Copy link
Collaborator

@expanded-for-real expanded-for-real commented Jun 27, 2025

#21

This was both easier and harder than I originally expected but finally got their. Uses the Unsafe memory buffer largely borrowed from Agrona/Fury to avoid things like boundary checking. This seems standard in other serialization libraries so was ultimately worth it.

Some to-dos - really need to consolidate some of the code better. There's a lot of duplication especially in how we parse and serialize headers.

Also this should pretty much be the end branch that resulted from constant rebasing so future MRs should look a little tidier.

expanded-for-real and others added 30 commits June 1, 2025 13:23
…mance tracking; add comprehensive String benchmark
Try to enhance string deserialization
A full list of enhancements can be found here - #3
…ome micro-optimizations added that were found along the way
* Full comprehensive comparison tests with a lot of other libraries + some micro-optimizations added that were found along the way

* replace deprecated gradle methods with latest

---------

Co-authored-by: expand3d <>
# Conflicts:
#	src/jmh/java/com/imprint/benchmark/ComparisonBenchmark.java
#	src/main/java/com/imprint/core/ImprintRecord.java
#	src/main/java/com/imprint/types/TypeHandler.java
#	src/main/java/com/imprint/types/Value.java
expand3d added 12 commits June 13, 2025 12:35
# Conflicts:
#	build.gradle
#	src/jmh/java/com/imprint/benchmark/ComparisonBenchmark.java
#	src/jmh/java/com/imprint/benchmark/ImprintDetailedBenchmark.java
#	src/jmh/java/com/imprint/benchmark/serializers/AvroSerializingBenchmark.java
#	src/jmh/java/com/imprint/benchmark/serializers/ImprintSerializingBenchmark.java
#	src/main/java/com/imprint/core/ImprintFieldObjectMap.java
#	src/main/java/com/imprint/core/ImprintRecord.java
#	src/main/java/com/imprint/core/ImprintRecordBuilder.java
#	src/main/java/com/imprint/ops/ImprintOperations.java
#	src/main/java/com/imprint/types/ImprintDeserializers.java
#	src/main/java/com/imprint/types/ImprintSerializers.java
#	src/main/java/com/imprint/types/MapKey.java
#	src/main/java/com/imprint/types/TypeCode.java
#	src/main/java/com/imprint/util/VarInt.java
#	src/test/java/com/imprint/ops/ImprintOperationsTest.java
#	src/test/java/com/imprint/profile/ProfilerTest.java
jmhImplementation 'org.msgpack:jackson-dataformat-msgpack:0.9.8'
jmhImplementation 'org.apache.thrift:libthrift:0.19.0'
jmhImplementation 'javax.annotation:javax.annotation-api:1.3.2'
jmhImplementation 'net.openhft:chronicle-wire:2.25ea5'
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got frustrated halfway through the effort and decided to add ChronicleWire as a comparison

@Fork(value = 1, jvmArgs = {"-Xms4g", "-Xmx4g"})
@Measurement(iterations = 10, time = 1)
@Fork(value = 1, jvmArgs = {"-Xms4g", "-Xmx4g",
"--illegal-access=permit",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ChronicleWire apparently needs all this


/**
* Specialized short→object map optimized for ImprintRecordBuilder field IDs.
* Basically a copy of EclipseCollections's primitive map:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to clean up javadocs everywhere still

@ToString(of = {"header"})
public class ImprintRecord {
ByteBuffer serializedBytes;
ImprintBuffer serializedBytes;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fortunately I was able to get the unsafe buffer to just be a drop in replacement for ByteBuffer so there's no functional changes here in the ImprintRecord

return this;
}

// Builder utilities
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused

// 2. Sort fields by ID for directory ordering (zero allocation)
// 2. Sort fields by ID for directory ordering
var sortedFieldsResult = getSortedFieldsResult();
var sortedValues = sortedFieldsResult.values;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole block I removed was hacky anyways but also unneeded - the ImprintBuffer is auto-expandable now and handles capacity increases so we don't have to worry too much about it

@@ -4,375 +4,393 @@
import com.imprint.core.*;
Copy link
Collaborator Author

@expanded-for-real expanded-for-real Jun 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was difficult but algoritmically everything is the same. I also decided to make merge and project their own static sub-class and separate out some other stuff to Core mostly as an effort to try to organize things (I still need to clean this up eventually since there's a lot of duplication). As I said though - algorithmically nothing has changed here

@@ -2,11 +2,14 @@

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No change here really - just drop in replacement


// Rough size estimate since actual takes time; might be able to accomodate this better with a growable buffer though

public static int estimateSize(TypeCode typeCode, Object value) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debating if I even need this size estimate now since the ImprintBuffer is auto-growable. Can just remove it later if I feel like it

@@ -0,0 +1,605 @@
package com.imprint.util;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly borrowed concepts from Agrona and Fury for this

@expanded-for-real
Copy link
Collaborator Author

@agavra latest benchmark numbers are here - #21

Serialization is all over the place, like +/- a whole microsecond sometimes. I'd have to run in a less noisy environment (like as in not my PC) to get more consistent numbers I think

@agavra
Copy link
Contributor

agavra commented Jul 8, 2025

@expanded-for-real sorry for the delay in reviews, pretty busy at work -- will take a look at this this week!

Copy link
Contributor

@agavra agavra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the production code (particularly the changes to ImprintRecordBuilder and the new ImprintBuffer) and the strategy looks good to me! Since the PR is a little big let me know if there's an area of the code you'd like me to focus a review on.

@expanded-for-real expanded-for-real merged commit bc6f6c4 into main Jul 14, 2025
3 checks passed
@expanded-for-real expanded-for-real deleted the custom-buffers branch July 14, 2025 23:35
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants