Is the unsafe version doing bounds-checking? I imagine de-serializing is much faster without branching, although probably not a great idea.
Hadoop's IPC protocol does something similar to what this article describes. They roll their own serialization format[0] and write to a wrapped ByteArrayOutputStream[1].
You use unsafe.putLong(), unsafe.putByte(), unsafe.putInt() to directly store a value into a given memory address. You then use unsafe.getLong() etc to directly read those values back from memory. This get you native C-like performance of only 78 ns, roughly 130 times faster than Java's 11,000 ns, very impressive! But when it comes to allocating memory, you use "new byte[1024]", a straight Java call. Using malloc ie. unsafe.allocateMemory(...), to get a larger byteblock and writing into it would probably speed up the routine even more. You also have the free ie. unsafe.freeMemory for the cleanup. (http://www.docjar.com/docs/api/sun/misc/Unsafe.html )
Network i/o puts a much bigger constraint on latency and throughput than serialization, so in practically all cases serializing to ByteBuffers using the standard ByteBuffer API (without resorting to Unsafe) would be just fine.
CPU can easily be the throughput bottleneck if you add enough network bandwidth.
It doesn't just happen in Java, it happens in C++ stuff too, and it can even be the throughput bottleneck for data going from the right kind of disk to the network -- that's why zero-copy I/O exists.
Yikes... using pointers in Java, definitely not the best idea. Like other poster said this is all meaningless if you are just blocking waiting for network IO, and there are some pretty fast serialization frameworks around: https://github.com/eishay/jvm-serializers/wiki/Staging-Resul...
Most of those frameworks you reference are much greater than 1 microsecond round trip which is the same ballpark as good network IO. For IPC this is much less. I operate in the financial low-latency space where this matters.
This is an apples-to-oranges comparison. The author admits this, and I'm not finding fault with the analysis. I do worry that those not familiar with various ser/deser schemes for Java will draw the wrong conclusions. My take-away is that you should not blindly use Java serialization in cases where performance matters and you don't need the broad spectrum of use cases it covers.
The main reason that Java serialization is so slow is that it is designed to persist self-describing, arbitrary object graphs.
Self-describing = Full package+class names and versions. Any object graph containing Serializable classes available via the classloaders of both the sender and receiver.
Arbitrary object graphs = The identity of objects is preserved. Let's say that 17 SalesOrder objects point to the same Customer object. salesOrderA.getCustomer() == salesOrderB.getCustomer() (identity in Java for those who haven't had the pleasure) is maintained.
Oh, and JDK dynamic proxies are handled as a special case. And some other stuff, too. I had the pleasure a few months ago of copying the JDK's serialization code into my own package structure and adding logging so we could debug deser issues in an OSGi environment. From what I can tell, the impedence mismatch between OSGi classloading hierarchies and Java Serialization makes this combination a non-starter.
The very-performant sample case provided in the blog posting works only for a list of flat objects. As soon as you try to preserve identity, you must implement a two-pass system where you deser placeholders and then resolve them later. I'm not suggesting that it isn't useful, but it probably constrains your serializable objects to such a degree that you'll be unhappy in other areas of your system.
Kyro (as another commenter noted) seems like the best combination of small size/speed/general-ness that I've encountered thus far. There seem to be a reasonable number of pre-built Kyro serializers for common libraries such as Joda time.
For a project I worked on recently where size was more important than speed, I took advantage of the similarity of the objects I was serializing and wrote a custom serializer which created dictionary look-ups on the fly. Repeated strings were turned into small integers, etc. I wish I didn't have to write it.
In a nutshell, if you constrain the scope of the problem you are attempting to solve, the maximum possible performance of the system across covered use-cases goes up. Java Serialization's performance is relatively poor compared to less generalized schemes -- as expected.
11 comments
[ 2.5 ms ] story [ 29.0 ms ] threadHadoop's IPC protocol does something similar to what this article describes. They roll their own serialization format[0] and write to a wrapped ByteArrayOutputStream[1].
[0]: Example of a serialized object: https://github.com/apache/hadoop-common/blob/fdf697eebc95cbb...
[1]: https://github.com/apache/hadoop-common/blob/fdf697eebc95cbb...
If you check the code you will see the buffer is only allocated once so the cost is not an issue.
It doesn't just happen in Java, it happens in C++ stuff too, and it can even be the throughput bottleneck for data going from the right kind of disk to the network -- that's why zero-copy I/O exists.
kryo - Fast, efficient Java serialization and cloning
http://code.google.com/p/kryo/
It'd be fun to see how kryo stacks against native C++.
The main reason that Java serialization is so slow is that it is designed to persist self-describing, arbitrary object graphs.
Self-describing = Full package+class names and versions. Any object graph containing Serializable classes available via the classloaders of both the sender and receiver.
Arbitrary object graphs = The identity of objects is preserved. Let's say that 17 SalesOrder objects point to the same Customer object. salesOrderA.getCustomer() == salesOrderB.getCustomer() (identity in Java for those who haven't had the pleasure) is maintained.
Oh, and JDK dynamic proxies are handled as a special case. And some other stuff, too. I had the pleasure a few months ago of copying the JDK's serialization code into my own package structure and adding logging so we could debug deser issues in an OSGi environment. From what I can tell, the impedence mismatch between OSGi classloading hierarchies and Java Serialization makes this combination a non-starter.
The very-performant sample case provided in the blog posting works only for a list of flat objects. As soon as you try to preserve identity, you must implement a two-pass system where you deser placeholders and then resolve them later. I'm not suggesting that it isn't useful, but it probably constrains your serializable objects to such a degree that you'll be unhappy in other areas of your system.
Kyro (as another commenter noted) seems like the best combination of small size/speed/general-ness that I've encountered thus far. There seem to be a reasonable number of pre-built Kyro serializers for common libraries such as Joda time.
For a project I worked on recently where size was more important than speed, I took advantage of the similarity of the objects I was serializing and wrote a custom serializer which created dictionary look-ups on the fly. Repeated strings were turned into small integers, etc. I wish I didn't have to write it.
In a nutshell, if you constrain the scope of the problem you are attempting to solve, the maximum possible performance of the system across covered use-cases goes up. Java Serialization's performance is relatively poor compared to less generalized schemes -- as expected.