155. Introducing memory segment view VarHandle
Let’s consider the following simple memory segment for storing an int (arena is an instance of Arena):
MemorySegment segment = arena.allocate(ValueLayout.JAVA_INT);
We know that we can create a VarHandle via PathElement:
// VarHandle[varType=int,
// coord=[interface java.lang.foreign.MemorySegment]]
VarHandle handle = ValueLayout.JAVA_INT.varHandle();
Or, via arrayElementVarHandle():
// VarHandle[varType=int,
// coord=[interface java.lang.foreign.MemorySegment, long]]
VarHandle arrhandle
= ValueLayout.JAVA_INT.arrayElementVarHandle();
The MethodHandles.memorySegmentViewVarHandle(ValueLayout layout) is another approach for creating a VarHandle that can be used to access a memory segment. The returned VarHandle perceives/views the content of the memory segment as a sequence of the given ValueLayout. In our case, the code looks as follows:
// VarHandle[varType=int,
// coord=[interface java.lang.foreign.MemorySegment, long]]
VarHandle viewhandle = MethodHandles
.memorySegmentViewVarHandle(ValueLayout.JAVA_INT);
Next, we can rely on insertCoordinates(VarHandle target, int pos, Object… values) to specify the set of bound coordinates before the VarHandle is actually invocated. In other words, the returned VarHandle will expose fewer coordinate types (CTs) than the given target.In our example, the target argument (invoked after inserting the set of bound coordinates) is viewhandle. The position of the first coordinate is 1, and we have a single bound coordinate representing the offset 0 of type long.
viewhandle = MethodHandles
.insertCoordinates(viewhandle, 1, 0);
Now, when we call the popular VarHandle.set/get(Object…) on the returned VarHandler, the incoming coordinate values are automatically joined with the given bound coordinate values. The result is passed to the target VarHandle.
viewhandle.set(segment, 75);
System.out.println(“Value: ” + viewhandle.get(segment));
Done! Now, you know three ways to create a VarHandle for dereferencing a memory segment.
156. Streaming memory segments
Combining Java Stream API with memory segments can be achieved via the elements(MemoryLayout elementLayout) method. This method gets an element layout and returns a Stream<MemorySegment> which is a sequential stream over disjoint slices in this segment. The stream size matches the size of the specified layout.Let’s consider the following memory layout:
SequenceLayout xy = MemoryLayout
.sequenceLayout(2, MemoryLayout.structLayout(
ValueLayout.JAVA_INT.withName(“x”),
ValueLayout.JAVA_INT.withName(“y”)));
Next, we declare two VarHandle and set some data:
VarHandle xHandle = xy.varHandle(
PathElement.sequenceElement(),
PathElement.groupElement(“x”));
VarHandle yHandle = xy.varHandle(
PathElement.sequenceElement(),
PathElement.groupElement(“y”));
try (Arena arena = Arena.openShared()) {
MemorySegment segment = arena.allocate(xy);
xHandle.set(segment, 0, 5);
yHandle.set(segment, 0, 9);
xHandle.set(segment, 1, 6);
yHandle.set(segment, 1, 8);
// stream operations
}
Let’s assume that we want to sum up all data. For this we can do it as follows:
int sum = segment.elements(xy)
.map(t -> t.toArray(ValueLayout.JAVA_INT))
.flatMapToInt(t -> Arrays.stream(t))
.sum();
Or, we can simply pass the proper layout and even empower parallel processing:
int sum = segment.elements(ValueLayout.JAVA_INT).parallel()
.mapToInt(s -> s.get(ValueLayout.JAVA_INT, 0))
.sum();
Both approaches return 28 = 5 + 9 + 6 + 8.How about summing only the values from the first (x, y) pair? For this we have to slice the layout corresponding to the first (x, y) pair via sliceHandle() – we introduced this method in Problem x:
MethodHandle xyHandle
= xy.sliceHandle(PathElement.sequenceElement());
Next, we slice the segment of the first (x, y) pair (if we replace 0 with 1 then we obtain the segment of the second (x, y) pair):
MemorySegment subsegment
= (MemorySegment) xyHandle.invoke(segment, 0);
And, we use it to calculate the needed sum:
int sum = subsegment.elements(ValueLayout.JAVA_INT).parallel()
.mapToInt(s -> s.get(ValueLayout.JAVA_INT, 0))
.sum();
The result is clear, 14 = 5 + 9.How about summing y from the first pair with the second pair (x, y)? For this, we can slice the proper segment via asSlice() – we introduced this method in Problem x:
var sum = segment.elements(xy).parallel()
.map(t -> t.asSlice(4).toArray(ValueLayout.JAVA_INT))
.flatMapToInt(t -> Arrays.stream(t))
.sum();
The asSlice(4) simply skip the first x since this is stored at offset 0 and consumes 4 bytes. From offset 4 to the end we have the first y, and the second pair (x, y). So, the result is 23 = 9 + 6 + 8.Notice that, this time, we have used a shared arena (Arena.openShared()). This is needed for parallel computations since the segment should be shared between multiple threads.Done! Feel free to challenge yourself to solve more such scenarios.