Slicing Allocators and Slicing Memory Segments

A slicing allocator returns a segment allocator that responds to allocation requests by returning consecutive contiguous regions of memory, or slices, obtained from an existing memory segment. You can also obtain a slice of a memory segment of any location within a memory segment with the method MethodSegment::asSlice.

Slicing Allocators

The following example allocates a memory segment named segment that can hold 60 Java int values. It then uses a slicing allocator by calling SegmentAllocator.slicingAllocator(MemorySgement) to obtain ten consecutive slices from segment. The example allocates an array of five integers in each slice. After, it prints the contents of each slice.

    try (Arena arena = Arena.ofConfined()) {
        SequenceLayout SEQUENCE_LAYOUT =
            MemoryLayout.sequenceLayout(60L, ValueLayout.JAVA_INT);
        MemorySegment segment = arena.allocate(SEQUENCE_LAYOUT);
        SegmentAllocator allocator = SegmentAllocator.slicingAllocator(segment);
        
        MemorySegment s[] = new MemorySegment[10];
        
        for (int i = 0 ; i < 10 ; i++) {
            s[i] = allocator.allocateArray(
                ValueLayout.JAVA_INT, 1, 2, 3, 4, 5);
        }

        for (int i = 0 ; i < 10 ; i++) {
            int[] intArray = s[i].toArray(ValueLayout.JAVA_INT);
            System.out.println(Arrays.toString(intArray));
        }
        
    } catch (Exception e) {
        e.printStackTrace();
    }

You can use segment allocators as building blocks to create arenas that support custom allocation strategies. For example, if a large number of native segments will share the same bounded lifetime, then a custom arena could use a slicing allocator to allocate the segments efficiently. This lets clients enjoy both scalable allocation (thanks to slicing) and deterministic deallocation (thanks to the arena).

The following example defines a slicing arena that behaves like a confined arena but internally uses a slicing allocator to respond to allocation requests. When the slicing arena is closed, the underlying confined arena is closed, invalidating all segments allocated in the slicing arena.

To keep this example short, it implements only a subset of the methods of Arena and SegmentAllocator (which is a superinterface of Arena).

public class SlicingArena implements Arena {
    final Arena arena = Arena.ofConfined();
    final SegmentAllocator slicingAllocator;

    SlicingArena(MemoryLayout m) {
        slicingAllocator = SegmentAllocator.slicingAllocator(arena.allocate(m));
    }
        
    public MemorySegment allocateArray(
        ValueLayout.OfInt elementLayout, int... elements) {
        return slicingAllocator.allocateArray(elementLayout, elements);    
    }
        
    public MemorySegment.Scope scope() {
        return arena.scope();
    }        

    public void close() {
        arena.close();
    }
}

With this slicing arena, you can rewrite the first example in this section more succinctly:

        SequenceLayout SEQUENCE_LAYOUT =
            MemoryLayout.sequenceLayout(60L, ValueLayout.JAVA_INT);
        try (Arena slicingArena = new SlicingArena(SEQUENCE_LAYOUT)) {
            MemorySegment s[] = new MemorySegment[10];
            
            for (int i = 0 ; i < 10 ; i++) {
                s[i] = slicingArena.allocateArray(
                    ValueLayout.JAVA_INT, 1, 2, 3, 4, 5);
            }
            
            for (int i = 0 ; i < 10 ; i++) {
                int[] intArray = s[i].toArray(ValueLayout.JAVA_INT);
                System.out.println(Arrays.toString(intArray));
            }
            
        } catch (Exception e) {
            e.printStackTrace();
        }

Slicing Memory Segments

When a slicing allocator returns a slice, the slice's starting address is right after the end of the last slice that the slicing allocator returned. You can call MemorySegment.asSlice(long,long) to obtain a slice of a memory segment of any location within the memory segment and of any size, provided that slice's size stays within the spatial bounds of the original memory segment. The following example obtains a slice of a memory segment, then prints its contents:

        String s = "abcdefghijklmnopqrstuvwxyz";
        char c[] = s.toCharArray();

        MemorySegment textSegment = MemorySegment.ofArray(c);
        long b = ValueLayout.JAVA_CHAR.byteSize();
        long firstLetter = 5;
        long size = 6;

        MemorySegment fghijk = textSegment.asSlice(firstLetter*b, size*b);

        for (int i = 0; i < size; i++) {
            System.out.print((char)fghijk.get(ValueLayout.JAVA_CHAR, i*b)); 
        }            
        System.out.println();

This example prints the following output:

fghijk

The method MemorySegment.elements(MemoryLayout) returns a stream of slices whose size matches that of the specified layout. Multiple threads could work in parallel to access these slices. To do this, however, the memory segment has to be accessible from multiple threads. You can do this by associating the memory segment with a shared arena, which you can create with Arena::ofShared.

The following example sums all int values in a memory segment in parallel.

    void addRandomNumbers(int numElements) throws Throwable {
        
        int[] numbers = new Random().ints(numElements, 0, 1000).toArray();

        try (Arena arena = Arena.ofShared()) {
            SequenceLayout SEQUENCE_LAYOUT = MemoryLayout.sequenceLayout((long)numElements, ValueLayout.JAVA_INT);
            MemorySegment segment = arena.allocate(SEQUENCE_LAYOUT);
            MemorySegment.copy(numbers, 0, segment, ValueLayout.JAVA_INT, 0L, numElements);
     
            int sum = segment.elements(ValueLayout.JAVA_INT).parallel()
                      .mapToInt(s -> s.get(ValueLayout.JAVA_INT, 0))
                      .sum();
                      
            System.out.println(sum);
        }        
    }