Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PR] Add support for native memory compression and decompression #311

Open
wants to merge 30 commits into
base: master
Choose a base branch
from

Conversation

VladRodionov
Copy link

This PR introduces support for handling native memory buffers that are allocated using the sun.misc.Unsafe.allocateMemory API. With this update, it is now possible to compress and decompress data between two native memory buffers, as well as transfer data from a byte array to native memory and vice versa.

@VladRodionov
Copy link
Author

This feature is essential for any application which works with off heap memory directly.

@VladRodionov
Copy link
Author

Will add unit tests.

Copy link
Owner

@luben luben left a comment

Choose a reason for hiding this comment

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

LGTM. Left some nits on the code. Can you add some tests

Comment on lines +484 to +485
char *dst_buff = (char *) dst;
char *src_buff = (char *) src;
Copy link
Owner

Choose a reason for hiding this comment

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

this should be void *

Comment on lines +477 to +478
if (NULL == (void *) dst) return -ZSTD_error_memory_allocation;
if (NULL == (void *) src) return -ZSTD_error_memory_allocation;
Copy link
Owner

Choose a reason for hiding this comment

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

may be move these below, after you cast dst to dst_buff

Comment on lines +839 to +840
char *dst_buff = (char *) dst;
char *src_buff = (char *) src;
Copy link
Owner

Choose a reason for hiding this comment

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

void *

Comment on lines +832 to +833
if (NULL == (void *) dst) return -ZSTD_error_memory_allocation;
if (NULL == (void *) src) return -ZSTD_error_memory_allocation;
Copy link
Owner

Choose a reason for hiding this comment

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

move below

Copy link

codecov bot commented May 3, 2024

Codecov Report

Attention: Patch coverage is 0% with 54 lines in your changes missing coverage. Please review.

Project coverage is 57.88%. Comparing base (c76455c) to head (b904897).
Report is 10 commits behind head on master.

Current head b904897 differs from pull request most recent head c75f02f

Please upload reports for the commit c75f02f to get more accurate results.

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #311      +/-   ##
============================================
- Coverage     60.01%   57.88%   -2.13%     
- Complexity      308      312       +4     
============================================
  Files            26       26              
  Lines          1473     1541      +68     
  Branches        170      186      +16     
============================================
+ Hits            884      892       +8     
- Misses          434      494      +60     
  Partials        155      155              

@VladRodionov
Copy link
Author

Sure, will add test this weekend. Thank you for the review @luben

@@ -0,0 +1,27 @@
#!/bin/bash

Copy link
Owner

Choose a reason for hiding this comment

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

Can you make this filename more descriptive and put a comment on top? Or just don't include it in the commit?

Copy link
Author

@VladRodionov VladRodionov May 30, 2024

Choose a reason for hiding this comment

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

I will remove this file from the commit. I had tried to add tests to this feature, but it has turned out that it's not that straightforward task. I had to enable sun.misc.Unsafe access to be able to allocate native memory and Scala (sbt) just makes this impossible (at least for me). I have zero experience in Scala and its tooling. In Java 9+ the access to this class is prohibited by default, so you have to specify additional command line args:
java --add-opens jdk.unsupported/sun.misc=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED

For some reason this does not work with sbt. Probably I did something wrong.

Copy link
Owner

Choose a reason for hiding this comment

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

Can you try to add these options to https://github.com/luben/zstd-jni/blob/master/build.sbt#L39 . I just pushed a change to run the tests in forked JVM so these options will apply.

Copy link
Author

@VladRodionov VladRodionov Jun 1, 2024

Choose a reason for hiding this comment

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

I recently moved repo fork to a new owner (organization) - https://github.com/carrotdata/zstd-jni/tree/master. The current PR is in some kind of a zombie state after that. I am going to close this PR and open new one, @luben . What do you think?

Copy link
Author

Choose a reason for hiding this comment

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

install-jar.sh has been removed.

Copy link
Owner

Choose a reason for hiding this comment

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

I recently moved repo fork to a new owner (organization) - https://github.com/carrotdata/zstd-jni/tree/master. The current PR is in some kind of a zombie state after that. I am going to close this PR and open new one, @luben . What do you think?

Sure, we can continue on a new PR

Copy link
Author

@VladRodionov VladRodionov Jun 1, 2024

Choose a reason for hiding this comment

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

It looks like we can keep the old one. I removed 'install-jar.sh and rebased it to the luben:master.

Copy link
Owner

@luben luben Jun 18, 2024

Choose a reason for hiding this comment

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

I just pushed a change to run the tests in forked JVM so these options will apply.

Hmm, it was not so easy - forking broke builds on Windows and Mac OS, not sure why. So I reverted that

@luben
Copy link
Owner

luben commented Jun 5, 2024

These binary files should not be checked in git - I re-build them on each supported platform for each release.

@VladRodionov
Copy link
Author

Files have been removed.

@joakime
Copy link

joakime commented Aug 5, 2024

Basing anything off sun.misc.Unsafe behavior is not a good idea anymore. It has been deprecated since 2006.
There are 2 active JEPs that are almost done with their implementations and rollout in OpenJDK.

@VladRodionov
Copy link
Author

It s long way to go until all Java code with direct sun.misc.Unsafe access will be ported to JDK 21+ (Java FFM), meanwhile we need to support JDK 11+ at least. Performance - wise Unsafe is still the champion, at least for direct memory access.

@joakime
Copy link

joakime commented Aug 5, 2024

Performance wise, Unsafe no longer wins.
Eclipse Jetty removed Unsafe a few year ago, and the various performance metrics has improved.

@VladRodionov
Copy link
Author

Jetty? Can it handle 500K+ RPS out of the box? Really doubt :). JFF is finally on par with JNI or slightly better, but for direct memory access and manipulations of bits and bytes outside of Java heap, Unsafe is the champ. And you missed my reqs - JDK 11+ support (actually Java 8+).
Java 2024 report - almost 30% are still using Java 8, the rest - Java 11 and Java 17, all of them are missing JFFM support.

@joakime
Copy link

joakime commented Aug 6, 2024

500K+ requests per second is not hard to do.
You have to be mindful of network saturation in regards to request/response size and optional http details.

This has been done on an official release of Eclipse Jetty 10, and Jetty 11, and Jetty 12 servers (all of which do not have Unsafe operations anymore).

The setup is as follows ...

  • The requests themselves should be sized to not overload whatever network limits you have. (so no testing of 1MB payloads!). I usually just pick a small 1 line quote from somebody famous (like Mark Twain) as the payload on the response (or request, depends on what I'm testing)
  • Test the server on a physical machine (not a vm or docker or cloud).
  • A sufficient number of separate physical clients to generate the load (about 5 clients per 1 server being tested to start, scale up the number clients slowly based network saturation at server and switch, which in usual testing at home with my equipment is about 320MB/s)
  • Clients MUST be on separate machines size, quality, and speed of machines is mostly irrelevant.
  • Clients MUST read/write fully and follow the HTTP/1.1 spec.
  • Clients SHOULD use HTTP/1.1 with persistent connections. (this turns into a MUST for TLS)
  • Clients SHOULD be configured for minimum headers (eg: drop Accept, User-Agent headers. If you can end up with just a method, path, and host that's the optimal setup for raw requests per second)
  • Disable server logging (even for console).
  • Default server configuration of http and server modules is usually sufficient to hit 400K second. You can cross the 500K second threshold by turning off various http features (example: turn off the production of the Server header, and Date header).
  • On rare occasions, depending on the payloads the max threads should be bumped up.
  • Server side request/response exchange handling should not have a code path that depends on filesystem I/O. (far too slow for this kind of testing)

This setup results in sub 50 byte requests, and sub 200 byte responses (or about 120 bytes for request on network, and 280 bytes for response on network), which is only really useful for load testing the server for requests per second and latency metrics.

When I monitor (with something like wireshark) with 1 client to confirm its setup, I'm looking at the total bytes on the network and wanting something sub 400 bytes per request/response exchange and no FIN (we should be using persistent connections).

Hitting 510k requests per second is very attainable on a 10GbE network against a Jetty Server with a decent networking interface (some crappy 10GbE interfaces cannot get close to even 20% saturation).

Java 8 went EOSL in many contexts already. (eg: google cloud dropped it Jan 2024)
Many Java 11 providers have it going EOSL at the end of this year too (eg: redhat in october, google in december)

@VladRodionov
Copy link
Author

VladRodionov commented Aug 6, 2024

https://medium.com/deno-the-complete-reference/netty-vs-jetty-hello-world-performance-e9ce990a9294

far from 510K RPS. May be its attainable, may be its - not. Not, I presume. Any Java network server which utilizes any type of thread pool executors will be handicapped due to significant thread context switch overhead. You are free to share links, which confirm, that 510K RPS is attainable for Jetty. I have not managed to find any proof of that statement, quite contrary, I found many benchmarks with a very abysmal performance and latency numbers. I am the developer of the Memcarrot - memcached-compatible caching server, written in Java with a heavy dosage of sun.misc.Unsafe. All memory management is manual (malloc(), free()). The server can run in less than 100MB of java heap while storing hundreds of millions of cached objects. Below are yesterday's test results (standard testing tool - memtier_benchmark was used):

parallels@ubuntu-linux-22-04-02-desktop:~$ memtier_benchmark -p 11211 -P memcache_text --test-time=100
Writing results to stdout
[RUN #1] Preparing benchmark client...
[RUN #1] Launching threads now...
[RUN #1 100%, 100 secs]  0 threads:    53594005 ops,  535766 (avg:  535922) ops/sec, 15.35MB/sec (avg: 15.31MB/sec),  0.37 (avg:  0.37) msec latency

4         Threads
50        Connections per thread
100       Seconds


ALL STATS
============================================================================================================================
Type         Ops/sec     Hits/sec   Misses/sec    Avg. Latency     p50 Latency     p99 Latency   p99.9 Latency       KB/sec 
----------------------------------------------------------------------------------------------------------------------------
Sets        48721.12          ---          ---         0.37491         0.35900         0.66300         0.93500      3325.15 
Gets       487201.32       634.00    486567.32         0.37314         0.35900         0.66300         0.94300     12355.39 
Waits           0.00          ---          ---             ---             ---             ---             ---          --- 
Totals     535922.44       634.00    486567.32         0.37331         0.35900         0.66300         0.94300     15680.54 

This is 535K RPS with p99.9 latency less than 1ms. These numbers are within 5% of native memcached. The test have been run on Mac Studio M1 (64GB RAM).

Other benchmark results (memory consumption, surprise, surprise) are here:
https://github.com/carrotdata/membench

Memcarrot will be released next week. sun.misc.Unsafe made it possible. This is why we need direct access to off heap memory and I am not sure that the code can we rewritten with JFFM API.

@joakime
Copy link

joakime commented Aug 6, 2024

https://medium.com/deno-the-complete-reference/netty-vs-jetty-hello-world-performance-e9ce990a9294

An unconfigured Jetty and testing on the same machine, that person just tested the performance of their localhost network stack, nothing else. That is a horrible set of tests and doesn't test performance of Jetty.
Using jetty-maven-plugin:run which is focused on developer needs by its configuration, not performance.
The configuration they used also did zero for tuning the http exchange.
I bet their Jetty server was barely being used, they simply couldn't generate enough load (a super common scenario when attempting to load test on the same machine).

Any Java network server which utilizes any type of thread pool executors will be handicapped due to significant thread context switch overhead.

Jetty doesn't use native JVM thread pool executors, it's got it's own and a EatWhatYouKill model that minimizes thread context switching, we even see improvements on CPU caching with this model.

When we participated in the TechEmpower benchmarks years ago (back in Jetty 10.0.0 days) we were consistently in the top 5%, and when we learned the tricks of the those above us we could easily get into the top 3%, but those tricks were not representing real world scenarios.

@joakime
Copy link

joakime commented Aug 6, 2024

I am the developer of the Memcarrot - memcached-compatible caching server, written in Java with a heavy dosage of sun.misc.Unsafe. All memory management is manual (malloc(), free()). The server can run in less than 100MB of java heap while storing hundreds of millions of cached objects. Below are yesterday's test results (standard testing tool - memtier_benchmark was used):

Congrats, that's a really fantastic outcome.

Anyway, this is devolved into a totally different set of arguments.
Do what you want. It is your repo after all.

Eclipse Jetty just has to monitor how the new JVMs react to our usage of the current state of zstd-jni. (so far it looks like we have to, at a minimum, document the demands that zstd-jni put to ByteBufferPool implementation, and the JVM command line switches necessary to allow zstd-jni to function.)

Copy link
Owner

Choose a reason for hiding this comment

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

please, don't put binaries in the source code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants