[External] : Re: jstack, profilers and other tools

Ron Pressler ron.pressler at oracle.com
Sun Jul 24 14:39:39 UTC 2022

On 24 Jul 2022, at 14:05, Alex Otenko <oleksandr.otenko at gmail.com<mailto:oleksandr.otenko at gmail.com>> wrote:

I am not sure what made you think I am fighting for async style everywhere. I am merely pointing out that some tasks are harder to solve in sync code - and impossible to solve, if you rely on library/built-in that isn't doing what you want. Like, if JDK doesn't expose a mechanism to prioritize reads, you can't solve the problem of reading fewer requests completely vs more requests partially.

I disagree with your claims, and the JDK exposes as many mechanisms to prioritise threads as it does to prioritise callback calls, and composition of synchronous constructs is easier in Java.

Also, I am not clear why you dismissed the allocation problem by just saying IO buffering is the same. The allocation problem is that user code allocates before read(byte[]) is called. This is the same in both sync and async code. The difference is that in async code the lifespan of the allocation is shorter - we read only read-ready channels, and those that aren't ready return quickly. In sync code the buffer remains allocated for a long time - even seconds, if that's what the connection reuse pattern is on the client side.

I am not dismissing it, merely claiming it is untrue. The JDK provides the same mechanisms that would allow synchronous and asynchronous I/O libraries to allocate buffers only for read-ready channels. It is true that the JDK’s built-in synchronous I/O constructs don’t do that, but the JDK’s built-in asynchronous don’t do that either. So both are equally possible, and both are equally not done by the JDK itself.

— Ron

On Sat, 23 Jul 2022, 02:00 Ron Pressler, <ron.pressler at oracle.com<mailto:ron.pressler at oracle.com>> wrote:

> On 23 Jul 2022, at 00:06, Alex Otenko <oleksandr.otenko at gmail.com<mailto:oleksandr.otenko at gmail.com>> wrote:
> I am familiar with the bijection between types and continuation-passing. There is however a barrier. Who does the thing of interest in each of the styles: the caller or the callee.
> In terms of type theory it doesn't matter; both styles have the same power. In terms of software engineering it does matter. It is impossible to plug custom logic in a library that you import, unless a mechanism is planned at design time, and built in - most commonly a bunch of configuration options to tweak a parameterised algorithm; not really the ability to implement arbitrary algorithms that the theoretical bijection requires. So in practice you are limited to doing only things a caller can do.
> Same for allocation. It is doable to get the same behaviour as in async API, but it is not what InputStream.read(byte[]) does - the method signature forces you to allocate and wait. And there is no mechanism to tell the blocking API to read 10k of 100 requests instead of 100 bytes of 10k requests. It's doable, but it is not there.

No, all that is simply incorrect. The customisability of synchronous code is at least as ergonomic and flexible as for async code if not more so (because Java is built around synchronous primitives, and its basic composition operators are made for synchronous primitives), and the I/O buffering primitives provided by the JDK are no different between synchronous and asynchronous.

I feel like you’re trying to find some justification for your aesthetic preference for asynchronous code, and there’s really no need. If you enjoy it more, and you don’t need the observability support from the runtime — by all means keep using it. Our goal isn’t to get fans of asynchronous code to abandon using it. It is to give the same benefits to those who prefer using synchronous code, and we can even go further because it is a better fit for the design of the Java platform and so that’s where we can support the code better, both in the runtime and the language. But if you like async better — use async.

But while we’re in the weeds, there are some interesting differences re memory usage, although they are not fundamentally about sync vs async, but some technical specifics of the JDK and how virtual threads are implemented. This might be of interest to the curious.

Whether you use the async or sync style, there’s need to pass data from computation done before the call to computation done after. In Java, the async style normally requires allocating a new object to do that, while sync reuses the same mutable stack. Hypothetically, async code could implement such a mutable stack manually, but in Java it is difficult, because, unlike in C, heap objects cannot switch between storing pointers and primitives in the same memory cell. Stacks are designed to allow that thanks to special GC protocols, that were adapted for virtual threads.

On the other hand, for simplicity and performance reasons in the JIT compiler, the way data is stored in the stack is wasteful (so fewer but bigger objects are allocated). We’re now working on making it more compact in the case of virtual threads.

— Ron

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/loom-dev/attachments/20220724/38124225/attachment.htm>

More information about the loom-dev mailing list