For (nearly) all memory that Flink accumulates (in the form of sort buffers, hash tables, caching), we use a special way of representing data serialized across a set of memory pages. The big work lies in the way the algorithms are implemented to operate on pages, rather than on objects.
The core class for the memory is the MemorySegment, which has all methods to set and get primitives values efficiently. It is a somewhat simpler (and faster) variant of a HeapByteBuffer.
As such, it should be straightforward to create a version where the memory segment is not backed by a heap byte, but by memory allocated outside the JVM, in a similar way as the NIO DirectByteBuffers, or the Netty direct buffers do it.
This may have multiple advantages:
- We reduce the size of the JVM heap (garbage collected) and the number and size of long living alive objects. For large JVM sizes, this may improve performance quite a bit. Utilmately, we would in many cases reduce JVM size to 1/3 to 1/2 and keep the remaining memory outside the JVM.
- We save copies when we move memory pages to disk (spilling) or through the network (shuffling / broadcasting / forward piping)
The changes required to implement this are
- Add a UnmanagedMemorySegment that only stores the memory adress as a long, and the segment size. It is initialized from a DirectByteBuffer.
- Allow the MemoryManager to allocate these MemorySegments, instead of the current ones.
- Make sure that the startup script pick up the mode and configure the heap size and the max direct memory properly.
Since the MemorySegment is probably the most performance critical class in Flink, we must take care that we do this right. The following are critical considerations:
- If we want both solutions (heap and off-heap) to exist side-by-side (configurable), we must make the base MemorySegment abstract and implement two versions (heap and off-heap).
- To get the best performance, we need to make sure that only one class gets loaded (or at least ever used), to ensure optimal JIT de-virtualization and inlining.
- We should carefully measure the performance of both variants. From previous micro benchmarks, I remember that individual byte accesses in DirectByteBuffers (off-heap) were slightly slower than on-heap, any larger accesses were equally good or slightly better.