Class StructAccessor

java.lang.Object
express.mvp.roray.utils.functions.StructAccessor

public final class StructAccessor extends Object
Provides convenient accessors for reading and writing struct fields in a 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:

PatternSingle Field6 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 Details

    • of

      public static StructAccessor of(StructLayout layout)
      Creates a struct accessor for the given layout.
      Parameters:
      layout - The struct layout
      Returns:
      A new StructAccessor
    • layout

      public StructLayout layout()
      Gets the underlying struct layout.
    • byteSize

      public long byteSize()
      Gets the byte size of the struct.
    • allocate

      public MemorySegment allocate(Arena arena)
      Allocates a new instance of this struct.
      Parameters:
      arena - Arena to allocate from
      Returns:
      A zeroed memory segment of the struct's size
    • allocateArray

      public MemorySegment allocateArray(Arena arena, long count)
      Allocates an array of structs.
      Parameters:
      arena - Arena to allocate from
      count - Number of struct instances
      Returns:
      Memory segment for the array
    • fieldOffset

      public long fieldOffset(String fieldName)
      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

      public VarHandle varHandle(String fieldName)
      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

      public byte getByte(MemorySegment segment, String fieldName)
      Reads a byte field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • setByte

      public void setByte(MemorySegment segment, String fieldName, byte value)
      Writes a byte field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • getShort

      public short getShort(MemorySegment segment, String fieldName)
      Reads a short field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • setShort

      public void setShort(MemorySegment segment, String fieldName, short value)
      Writes a short field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • getInt

      public int getInt(MemorySegment segment, String fieldName)
      Reads an int field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • setInt

      public void setInt(MemorySegment segment, String fieldName, int value)
      Writes an int field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • getLong

      public long getLong(MemorySegment segment, String fieldName)
      Reads a long field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • setLong

      public void setLong(MemorySegment segment, String fieldName, long value)
      Writes a long field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • getFloat

      public float getFloat(MemorySegment segment, String fieldName)
      Reads a float field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • setFloat

      public void setFloat(MemorySegment segment, String fieldName, float value)
      Writes a float field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • getDouble

      public double getDouble(MemorySegment segment, String fieldName)
      Reads a double field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • setDouble

      public void setDouble(MemorySegment segment, String fieldName, double value)
      Writes a double field by name.

      Performance: ~57 ns/op (1.4x slower than extracted VarHandle). For hot paths, use varHandle(String) instead.

    • getPointer

      public MemorySegment getPointer(MemorySegment segment, String fieldName)
      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

      public void setPointer(MemorySegment segment, String fieldName, MemorySegment value)
      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

      public int getIntAt(MemorySegment segment, long offset)
      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

      public void setIntAt(MemorySegment segment, long offset, int value)
      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

      public long getLongAt(MemorySegment segment, long offset)
      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

      public void setLongAt(MemorySegment segment, long offset, long value)
      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

      public MemorySegment getPointerAt(MemorySegment segment, long offset)
      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

      public void setPointerAt(MemorySegment segment, long offset, MemorySegment value)
      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

      public MemorySegment elementAt(MemorySegment arraySegment, long index)
      Gets a slice representing the nth element in an array of structs.
      Parameters:
      arraySegment - The array segment
      index - Element index (0-based)
      Returns:
      Slice representing the struct at that index