Hi,

On Sun, Feb 18, 2018 at 9:12 PM, Chuck Davis <cjgun...@gmail.com> wrote:
> There is a great hush here regarding strategy for putting the pieces
> back together so here goes my tirade.
>
> Following is based on assumption of a 100k payload.
>
> With jdk8:
> The decoder received a ByteBuffer
> bais = new ByteArrayInputStream(ByteBuffer.array());  // wrap the
> buffer -- optional method but implemented in Oracle JVM
> ois = new ObjectInputStream(bais)  // wrap the input stream
> MyDTO = ois.readObject();   // deserialize the object (which may
> contain hundreds or thousands of  objects)
>
> DONE!  Simple, clean and blazingly fast.

Well, not so fast.
There are a lot of assumptions in the above code.
The first is that the ByteBuffer has a backing array, which may not be the case.
If you write the right code that takes care of the possibility that
the ByteBuffer does not have a backing array, you end up with another
copy and more code.
More importantly, the implementation would have done all the copies
for you in order to feed your code with a final ByteBuffer.
So you are not really skipping the copies and the performance is not
different from below, where the possible copies are explicit.

> With jdk9:
> The Listener receives a ByteBuffer (I read someplace the default size
> is potentially going to be 256 bytes)
> If it's the first part (or whole) create a byte[] of 10k (or whatever)
> Start copying bytes from the buffer to the array
> Check each byte to be sure there is room in the array for another byte
>     if room, copy the byte
>     if no room, copy the array to a larger array (current size + 10k)
> and then copy the byte
>     repeat until all messages are processed (i.e. each byte will have
> been copied somewhere between 1 and 10 times)
> bais = new ByteArrayInputStrea(byte[]);
> ois = new ObjectInputStream(bais);
> MyDTO = ois.readObject();
>
> DONE!  More complicated, messy and very slow.  (i.e. lots of wasted cpu 
> cycles)

Nah :)

ByteBufferInputStream bbis = new ByteBufferInputStream();
for each partial ByteBuffer received by the listener {
  bbis.add(byteBuffer);
}
ois = new ObjectInputStream(bbis);
MyDTO = ois.readObject();

Now, ByteBufferInputStream is not present in the JDK, and if you want
to complain you are in good (and conspicuous) company, as the JDK
engineers appear to avoid the issue since years now (e.g. create a
String from a ByteBuffer without copy).
Having said that, the class is trivial to implement (a naive version I
wrote is about 30 lines) and may even be provided as a utility class
by the JDK WebSocket implementers.

The advantage is that ByteBufferInputStream can be written in a way
that performs *zero* copies, thanks to the JDK 9 WebSocket APIs -
add() would need to return a CompletableFuture.
If you don't want to confront with CompletableFutures, it can be
written in a way that performs just one copy.

> RESULT:  The formerly fabulous WebSocket has been rendered relatively
> useless as a platform for building responsive, bidirectional
> client/server applications.  Am I the only person who sees this as a
> HUGE regression of functionality??  I am ALARMED by this turn of
> events.

Nah :)

To sum up:
* with JDK 9 you need a utility class that you did not need with JDK 8
* with JDK 9 you can actually perform a zero copy provided you put a
bit of effort (or the JDK guys do)
* with JDK 9 the number of byte copies is the same or better

All in all, anything you can do with the JDK 8 API can be done in JDK 9.
For some cases, JDK 9 is more efficient than JDK 8.
For all other cases it's on par.
JDK 9 requires a utility class to convert from ByteBuffer to InputStream.

Note also that your requirement is to use blocking, stream-based,
byte[]-based APIs.
If you had chosen a data format for which a non-blocking parser based
on ByteBuffer APIs existed, you would be so happy about the JDK 9
APIs.

> OPINION:
>
> I'm pretty naive about the world of midrange and mainframe except that
> they can throw a lot of bytes in a short time.  But I imagine the
> choke-point of any desktop application is going to be the network.
> Unless somebody is running a Pentium I doubt any relatively modern
> desktop is going to have a difficult time keeping up with networking
> speeds.  It seems to me, therefore, that backpressure in WebSocket is
> a solution looking for a problem.

I have a different experience, where clients may not be able to keep
up with the network.
This happens with browsers receiving a message and having to update a
gazillion DOM nodes (with all the ripple effects that this entails),
with clients generating so much garbage that the GC is heavily
interfering with their throughput, with proxy applications that have
different speeds on the 2 sides they proxy, and so on.

Here I am seriously advocating to the net-dev JDK group to provide an
efficient solution, or at least start a discussion where appropriate,
for ByteBuffer to [Input|Output]Stream that is sorely missing in the
JDK.

Thanks !

-- 
Simone Bordet
---
Finally, no matter how good the architecture and design are,
to deliver bug-free software with optimal performance and reliability,
the implementation technique must be flawless.   Victoria Livschitz

Reply via email to