Class StructAccessor
MemorySegment.
This class creates VarHandles for struct field access and provides type-safe
read/write methods. All setup costs are paid at construction time.
Performance Patterns (Fastest to Slowest)
This class supports three access patterns with different performance characteristics. Choose based on your performance requirements:
Pattern 1: Extracted VarHandles (Fastest - ~40 ns/op)
For hot paths requiring maximum performance, extract VarHandles at class initialization and use them directly. This achieves the same performance as hand-written VarHandle code.
// At class level - extract once
private static final StructAccessor SQE = StructAccessor.of(IO_URING_SQE_LAYOUT);
private static final VarHandle OPCODE_VH = SQE.varHandle("opcode");
private static final VarHandle FD_VH = SQE.varHandle("fd");
private static final VarHandle USER_DATA_VH = SQE.varHandle("user_data");
// In hot path - direct VarHandle access (~40 ns for single field)
void prepareOperation(MemorySegment sqe, byte opcode, int fd, long userData) {
OPCODE_VH.set(sqe, 0L, opcode);
FD_VH.set(sqe, 0L, fd);
USER_DATA_VH.set(sqe, 0L, userData);
}
Pattern 2: String-based Methods (Convenient - ~60-180 ns/op)
For non-critical paths where code clarity matters more than raw speed. Uses cached VarHandles internally but has HashMap lookup overhead per call.
StructAccessor sockaddr = StructAccessor.of(SOCKADDR_IN_LAYOUT);
// Setup code - readable but ~1.5-4x slower than Pattern 1
sockaddr.setShort(addr, "sin_family", AF_INET);
sockaddr.setShort(addr, "sin_port", port);
sockaddr.setInt(addr, "sin_addr", ipAddress);
Pattern 3: Offset-based Methods (Manual - ~40 ns/op)
For cases where you've pre-computed offsets. Same speed as Pattern 1 but requires manual offset management.
long userDataOffset = sqeAccessor.fieldOffset("user_data");
sqeAccessor.setLongAt(sqe, userDataOffset, value);
Performance Benchmark Results
Measured on io_uring SQE struct (64 bytes) with JMH, Java 25:
| Pattern | Single Field | 6 Fields (prepSend) |
|---|---|---|
| Direct VarHandle (baseline) | ~40 ns | ~47 ns |
| Pattern 1: Extracted VarHandles | ~38 ns | ~44 ns |
| Pattern 2: String-based methods | ~57 ns (+43%) | ~179 ns (+280%) |
Basic Usage
// Create accessor for sockaddr_in
StructAccessor sockaddrIn = StructAccessor.of(LinuxLayouts.SOCKADDR_IN);
try (Arena arena = Arena.ofConfined()) {
MemorySegment addr = sockaddrIn.allocate(arena);
sockaddrIn.setShort(addr, "sin_family", LinuxLayouts.AF_INET);
sockaddrIn.setShort(addr, "sin_port", Short.reverseBytes((short) 8080));
sockaddrIn.setInt(addr, "sin_addr", 0x7F000001); // 127.0.0.1
}
Array of Structs
StructAccessor iovec = StructAccessor.of(LinuxLayouts.IOVEC);
MemorySegment iovecs = iovec.allocateArray(arena, 3);
for (int i = 0; i < 3; i++) {
MemorySegment element = iovec.elementAt(iovecs, i);
iovec.setPointer(element, "iov_base", buffers[i]);
iovec.setLong(element, "iov_len", buffers[i].byteSize());
}
- See Also:
-
Method Summary
Modifier and TypeMethodDescriptionAllocates a new instance of this struct.allocateArray(Arena arena, long count) Allocates an array of structs.longbyteSize()Gets the byte size of the struct.elementAt(MemorySegment arraySegment, long index) Gets a slice representing the nth element in an array of structs.longfieldOffset(String fieldName) Gets the byte offset of a named field.bytegetByte(MemorySegment segment, String fieldName) Reads a byte field by name.doublegetDouble(MemorySegment segment, String fieldName) Reads a double field by name.floatgetFloat(MemorySegment segment, String fieldName) Reads a float field by name.intgetInt(MemorySegment segment, String fieldName) Reads an int field by name.intgetIntAt(MemorySegment segment, long offset) Reads an int at the given offset.longgetLong(MemorySegment segment, String fieldName) Reads a long field by name.longgetLongAt(MemorySegment segment, long offset) Reads a long at the given offset.getPointer(MemorySegment segment, String fieldName) Reads a pointer (address) field by name.getPointerAt(MemorySegment segment, long offset) Reads a pointer at the given offset.shortgetShort(MemorySegment segment, String fieldName) Reads a short field by name.layout()Gets the underlying struct layout.static StructAccessorof(StructLayout layout) Creates a struct accessor for the given layout.voidsetByte(MemorySegment segment, String fieldName, byte value) Writes a byte field by name.voidsetDouble(MemorySegment segment, String fieldName, double value) Writes a double field by name.voidsetFloat(MemorySegment segment, String fieldName, float value) Writes a float field by name.voidsetInt(MemorySegment segment, String fieldName, int value) Writes an int field by name.voidsetIntAt(MemorySegment segment, long offset, int value) Writes an int at the given offset.voidsetLong(MemorySegment segment, String fieldName, long value) Writes a long field by name.voidsetLongAt(MemorySegment segment, long offset, long value) Writes a long at the given offset.voidsetPointer(MemorySegment segment, String fieldName, MemorySegment value) Writes a pointer (address) field by name.voidsetPointerAt(MemorySegment segment, long offset, MemorySegment value) Writes a pointer at the given offset.voidsetShort(MemorySegment segment, String fieldName, short value) Writes a short field by name.Gets the cached VarHandle for a field.
-
Method Details
-
of
Creates a struct accessor for the given layout.- Parameters:
layout- The struct layout- Returns:
- A new StructAccessor
-
layout
Gets the underlying struct layout. -
byteSize
public long byteSize()Gets the byte size of the struct. -
allocate
Allocates a new instance of this struct.- Parameters:
arena- Arena to allocate from- Returns:
- A zeroed memory segment of the struct's size
-
allocateArray
Allocates an array of structs.- Parameters:
arena- Arena to allocate fromcount- Number of struct instances- Returns:
- Memory segment for the array
-
fieldOffset
Gets the byte offset of a named field.This method uses a pre-computed cache for O(1) lookup time.
- Parameters:
fieldName- Name of the field- Returns:
- Byte offset from struct start
- Throws:
IllegalArgumentException- if field not found
-
varHandle
Gets the cached VarHandle for a field.This is the recommended pattern for hot paths. Extract VarHandles at class initialization time and use them directly for maximum performance (~40 ns/op).
Usage Pattern
// Extract at class initialization (one-time cost) private static final StructAccessor SQE = StructAccessor.of(IO_URING_SQE_LAYOUT); private static final VarHandle OPCODE_VH = SQE.varHandle("opcode"); private static final VarHandle FD_VH = SQE.varHandle("fd"); private static final VarHandle ADDR_VH = SQE.varHandle("addr"); private static final VarHandle LEN_VH = SQE.varHandle("len"); private static final VarHandle USER_DATA_VH = SQE.varHandle("user_data"); // In hot path - direct VarHandle calls, JIT can fully inline void prepSend(MemorySegment sqe, int fd, long bufAddr, int len, long userData) { OPCODE_VH.set(sqe, 0L, IORING_OP_SEND); FD_VH.set(sqe, 0L, fd); ADDR_VH.set(sqe, 0L, bufAddr); LEN_VH.set(sqe, 0L, len); USER_DATA_VH.set(sqe, 0L, userData); }Performance
- Single field access: ~38 ns (same as hand-written VarHandle code)
- 6-field operation: ~44 ns
- Overhead vs direct VarHandle: <5% (within measurement noise)
- Parameters:
fieldName- Name of the field- Returns:
- VarHandle for the field, suitable for storing in a static final field
- Throws:
IllegalArgumentException- if field not found or has no VarHandle
-
getByte
Reads a byte field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
setByte
Writes a byte field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
getShort
Reads a short field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
setShort
Writes a short field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
getInt
Reads an int field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
setInt
Writes an int field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
getLong
Reads a long field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
setLong
Writes a long field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
getFloat
Reads a float field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
setFloat
Writes a float field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
getDouble
Reads a double field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
setDouble
Writes a double field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
getPointer
Reads a pointer (address) field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
setPointer
Writes a pointer (address) field by name.Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use
varHandle(String)instead. -
getIntAt
Reads an int at the given offset.Performance: ~40 ns/op (same as extracted VarHandle). Use when you've pre-computed the offset via
fieldOffset(String). -
setIntAt
Writes an int at the given offset.Performance: ~40 ns/op (same as extracted VarHandle). Use when you've pre-computed the offset via
fieldOffset(String). -
getLongAt
Reads a long at the given offset.Performance: ~40 ns/op (same as extracted VarHandle). Use when you've pre-computed the offset via
fieldOffset(String). -
setLongAt
Writes a long at the given offset.Performance: ~40 ns/op (same as extracted VarHandle). Use when you've pre-computed the offset via
fieldOffset(String). -
getPointerAt
Reads a pointer at the given offset.Performance: ~40 ns/op (same as extracted VarHandle). Use when you've pre-computed the offset via
fieldOffset(String). -
setPointerAt
Writes a pointer at the given offset.Performance: ~40 ns/op (same as extracted VarHandle). Use when you've pre-computed the offset via
fieldOffset(String). -
elementAt
Gets a slice representing the nth element in an array of structs.- Parameters:
arraySegment- The array segmentindex- Element index (0-based)- Returns:
- Slice representing the struct at that index
-