Skip to content

Commit 8362a35

Browse files
dfa1claude
andcommitted
refactor(reader): hoist Materialized* boilerplate into a shared base
The eight buffer-backed Materialized*Array classes each repeated the same cold plumbing: the dtype/length/buffer triple, the trivial dtype()/length() getters, and the zero-copy materialize()/segmentIfPresent() contract. Extract it into a package-private AbstractMaterializedArray that each leaf now extends. The base deliberately does NOT implement Array (that interface is sealed to the typed element families) and does NOT hoist the hot getX/fold/forEach loops: those stay monomorphic and branch-split in the leaf classes so C2 keeps vectorising them (CLAUDE.md hot-loop rule). Only the cold boilerplate moves. Net -206 LOC, kills the SonarQube duplication smell across the family. Reader suite green (854); javadoc enforcement passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b557e57 commit 8362a35

9 files changed

Lines changed: 69 additions & 275 deletions
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.github.dfa1.vortex.reader.array;
2+
3+
import io.github.dfa1.vortex.core.DType;
4+
5+
import java.lang.foreign.MemorySegment;
6+
import java.lang.foreign.SegmentAllocator;
7+
import java.util.Optional;
8+
9+
/// Shared cold-path plumbing for the buffer-backed `Materialized*` arrays: the
10+
/// `dtype` / `length` / `buffer` triple, the trivial accessors, and the zero-copy
11+
/// `materialize` / `segmentIfPresent` contract every variant shares.
12+
///
13+
/// Subclasses keep their own typed element access and the branch-split hot loops
14+
/// (`getX` / `fold` / `forEach`): those must stay monomorphic in the leaf class so the
15+
/// JIT can vectorise them, and so are deliberately not hoisted here. This base only
16+
/// holds the cold boilerplate.
17+
///
18+
/// Not `implements Array`: that interface is sealed to the typed element families
19+
/// ([IntArray], [LongArray], …), which each leaf implements directly while inheriting
20+
/// the common methods from here.
21+
abstract class AbstractMaterializedArray {
22+
23+
final DType dtype;
24+
final long length;
25+
final MemorySegment buffer;
26+
27+
AbstractMaterializedArray(DType dtype, long length, MemorySegment buffer) {
28+
this.dtype = dtype;
29+
this.length = length;
30+
this.buffer = buffer;
31+
}
32+
33+
public final DType dtype() {
34+
return dtype;
35+
}
36+
37+
public final long length() {
38+
return length;
39+
}
40+
41+
/// Returns the backing buffer directly — it is already the contiguous segment the
42+
/// array's materialize contract promises, so no copy or allocation is needed.
43+
///
44+
/// @param arena unused; the existing buffer is returned as-is
45+
/// @return the backing segment
46+
public final MemorySegment materialize(SegmentAllocator arena) {
47+
return buffer;
48+
}
49+
50+
public final Optional<MemorySegment> segmentIfPresent() {
51+
return Optional.of(buffer);
52+
}
53+
}

reader/src/main/java/io/github/dfa1/vortex/reader/array/MaterializedBoolArray.java

Lines changed: 2 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,19 @@
44
import io.github.dfa1.vortex.core.DType;
55

66
import java.lang.foreign.MemorySegment;
7-
import java.lang.foreign.SegmentAllocator;
87
import java.lang.foreign.ValueLayout;
9-
import java.util.Optional;
108

119
/// Buffer-backed [BoolArray] — the fallback used when an encoding decoder
1210
/// either materialises the output eagerly or has no lazy variant of its own.
13-
public final class MaterializedBoolArray implements BoolArray {
14-
15-
private final DType dtype;
16-
private final long length;
17-
private final MemorySegment buffer;
11+
public final class MaterializedBoolArray extends AbstractMaterializedArray implements BoolArray {
1812

1913
/// Constructs a `MaterializedBoolArray` backed by the given bit-packed buffer.
2014
///
2115
/// @param dtype logical type, must be [io.github.dfa1.vortex.core.DType.Bool]
2216
/// @param length number of logical boolean elements
2317
/// @param buffer bit-packed boolean data (LSB-first, one byte per 8 elements)
2418
public MaterializedBoolArray(DType dtype, long length, MemorySegment buffer) {
25-
this.dtype = dtype;
26-
this.length = length;
27-
this.buffer = buffer;
28-
}
29-
30-
@Override
31-
public DType dtype() {
32-
return dtype;
33-
}
34-
35-
@Override
36-
public long length() {
37-
return length;
38-
}
39-
40-
/// Returns the backing buffer directly — already an LSB-first packed bitmap,
41-
/// matching the format produced by [BoolArray#materialize(SegmentAllocator)],
42-
/// so no copy or allocation is needed.
43-
///
44-
/// @param arena unused; the existing buffer is returned as-is
45-
/// @return the backing LSB-first packed bitmap
46-
@Override
47-
public MemorySegment materialize(SegmentAllocator arena) {
48-
return buffer;
49-
}
50-
51-
@Override
52-
public Optional<MemorySegment> segmentIfPresent() {
53-
return Optional.of(buffer);
19+
super(dtype, length, buffer);
5420
}
5521

5622
@Override

reader/src/main/java/io/github/dfa1/vortex/reader/array/MaterializedByteArray.java

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,13 @@
55
import io.github.dfa1.vortex.core.PType;
66

77
import java.lang.foreign.MemorySegment;
8-
import java.lang.foreign.SegmentAllocator;
98
import java.lang.foreign.ValueLayout;
10-
import java.util.Optional;
119
import java.util.function.LongBinaryOperator;
1210

1311
/// Buffer-backed [ByteArray] — the fallback used when an encoding decoder
1412
/// either materialises the output eagerly or has no lazy variant of its own.
15-
public final class MaterializedByteArray implements ByteArray {
13+
public final class MaterializedByteArray extends AbstractMaterializedArray implements ByteArray {
1614

17-
private final DType dtype;
18-
private final long length;
19-
private final MemorySegment buffer;
2015
private final long elementCount;
2116

2217
/// Constructs a `MaterializedByteArray` backed by the given buffer.
@@ -25,37 +20,10 @@ public final class MaterializedByteArray implements ByteArray {
2520
/// @param length number of logical elements
2621
/// @param buffer raw byte data (one byte per element)
2722
public MaterializedByteArray(DType dtype, long length, MemorySegment buffer) {
28-
this.dtype = dtype;
29-
this.length = length;
30-
this.buffer = buffer;
23+
super(dtype, length, buffer);
3124
this.elementCount = buffer.byteSize();
3225
}
3326

34-
@Override
35-
public DType dtype() {
36-
return dtype;
37-
}
38-
39-
@Override
40-
public long length() {
41-
return length;
42-
}
43-
44-
/// Returns the backing buffer directly — already a contiguous one-byte-per-element
45-
/// segment, so no copy or allocation is needed.
46-
///
47-
/// @param arena unused; the existing buffer is returned as-is
48-
/// @return the backing byte segment
49-
@Override
50-
public MemorySegment materialize(SegmentAllocator arena) {
51-
return buffer;
52-
}
53-
54-
@Override
55-
public Optional<MemorySegment> segmentIfPresent() {
56-
return Optional.of(buffer);
57-
}
58-
5927
@Override
6028
public byte getByte(long i) {
6129
return buffer.get(ValueLayout.JAVA_BYTE, length == elementCount ? i : i % elementCount);

reader/src/main/java/io/github/dfa1/vortex/reader/array/MaterializedDoubleArray.java

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,13 @@
44
import io.github.dfa1.vortex.encoding.PTypeIO;
55

66
import java.lang.foreign.MemorySegment;
7-
import java.lang.foreign.SegmentAllocator;
8-
import java.util.Optional;
97
import java.util.function.DoubleBinaryOperator;
108
import java.util.function.DoubleConsumer;
119

1210
/// Buffer-backed [DoubleArray] — the fallback used when an encoding decoder
1311
/// either materialises the output eagerly or has no lazy variant of its own.
14-
public final class MaterializedDoubleArray implements DoubleArray {
12+
public final class MaterializedDoubleArray extends AbstractMaterializedArray implements DoubleArray {
1513

16-
private final DType dtype;
17-
private final long length;
18-
private final MemorySegment buffer;
1914
private final long elementCount;
2015

2116
/// Constructs a `MaterializedDoubleArray` backed by the given buffer.
@@ -24,37 +19,10 @@ public final class MaterializedDoubleArray implements DoubleArray {
2419
/// @param length number of logical elements
2520
/// @param buffer raw double data (8 bytes per element, little-endian)
2621
public MaterializedDoubleArray(DType dtype, long length, MemorySegment buffer) {
27-
this.dtype = dtype;
28-
this.length = length;
29-
this.buffer = buffer;
22+
super(dtype, length, buffer);
3023
this.elementCount = buffer.byteSize() / PTypeIO.LE_DOUBLE.byteSize();
3124
}
3225

33-
@Override
34-
public DType dtype() {
35-
return dtype;
36-
}
37-
38-
@Override
39-
public long length() {
40-
return length;
41-
}
42-
43-
/// Returns the backing buffer directly — already a contiguous little-endian
44-
/// `f64` segment, so no copy or allocation is needed.
45-
///
46-
/// @param arena unused; the existing buffer is returned as-is
47-
/// @return the backing little-endian `f64` segment
48-
@Override
49-
public MemorySegment materialize(SegmentAllocator arena) {
50-
return buffer;
51-
}
52-
53-
@Override
54-
public Optional<MemorySegment> segmentIfPresent() {
55-
return Optional.of(buffer);
56-
}
57-
5826
@Override
5927
public double getDouble(long i) {
6028
return buffer.getAtIndex(PTypeIO.LE_DOUBLE, length == elementCount ? i : i % elementCount);

reader/src/main/java/io/github/dfa1/vortex/reader/array/MaterializedFloat16Array.java

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,51 +5,18 @@
55
import io.github.dfa1.vortex.encoding.PTypeIO;
66

77
import java.lang.foreign.MemorySegment;
8-
import java.lang.foreign.SegmentAllocator;
9-
import java.util.Optional;
108

119
/// Buffer-backed [Float16Array] — the fallback used when an encoding decoder
1210
/// either materialises the output eagerly or has no lazy variant of its own.
13-
public final class MaterializedFloat16Array implements Float16Array {
14-
15-
private final DType dtype;
16-
private final long length;
17-
private final MemorySegment buffer;
11+
public final class MaterializedFloat16Array extends AbstractMaterializedArray implements Float16Array {
1812

1913
/// Creates a new `MaterializedFloat16Array` backed by the given memory segment.
2014
///
2115
/// @param dtype logical type, must be F16
2216
/// @param length number of elements
2317
/// @param buffer little-endian half-precision float data (2 bytes per element)
2418
public MaterializedFloat16Array(DType dtype, long length, MemorySegment buffer) {
25-
this.dtype = dtype;
26-
this.length = length;
27-
this.buffer = buffer;
28-
}
29-
30-
@Override
31-
public DType dtype() {
32-
return dtype;
33-
}
34-
35-
@Override
36-
public long length() {
37-
return length;
38-
}
39-
40-
/// Returns the backing buffer directly — already a contiguous little-endian
41-
/// half-precision segment (2 bytes per element), so no copy or allocation is needed.
42-
///
43-
/// @param arena unused; the existing buffer is returned as-is
44-
/// @return the backing little-endian `f16` segment
45-
@Override
46-
public MemorySegment materialize(SegmentAllocator arena) {
47-
return buffer;
48-
}
49-
50-
@Override
51-
public Optional<MemorySegment> segmentIfPresent() {
52-
return Optional.of(buffer);
19+
super(dtype, length, buffer);
5320
}
5421

5522
@Override

reader/src/main/java/io/github/dfa1/vortex/reader/array/MaterializedFloatArray.java

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,12 @@
55
import io.github.dfa1.vortex.encoding.PTypeIO;
66

77
import java.lang.foreign.MemorySegment;
8-
import java.lang.foreign.SegmentAllocator;
9-
import java.util.Optional;
108
import java.util.function.DoubleBinaryOperator;
119

1210
/// Buffer-backed [FloatArray] — the fallback used when an encoding decoder
1311
/// either materialises the output eagerly or has no lazy variant of its own.
14-
public final class MaterializedFloatArray implements FloatArray {
12+
public final class MaterializedFloatArray extends AbstractMaterializedArray implements FloatArray {
1513

16-
private final DType dtype;
17-
private final long length;
18-
private final MemorySegment buffer;
1914
private final long elementCount;
2015

2116
/// Creates a new `MaterializedFloatArray` backed by the given memory segment.
@@ -24,37 +19,10 @@ public final class MaterializedFloatArray implements FloatArray {
2419
/// @param length number of elements
2520
/// @param buffer little-endian float data (4 bytes per element)
2621
public MaterializedFloatArray(DType dtype, long length, MemorySegment buffer) {
27-
this.dtype = dtype;
28-
this.length = length;
29-
this.buffer = buffer;
22+
super(dtype, length, buffer);
3023
this.elementCount = buffer.byteSize() / PTypeIO.LE_FLOAT.byteSize();
3124
}
3225

33-
@Override
34-
public DType dtype() {
35-
return dtype;
36-
}
37-
38-
@Override
39-
public long length() {
40-
return length;
41-
}
42-
43-
/// Returns the backing buffer directly — already a contiguous little-endian
44-
/// `f32` segment, so no copy or allocation is needed.
45-
///
46-
/// @param arena unused; the existing buffer is returned as-is
47-
/// @return the backing little-endian `f32` segment
48-
@Override
49-
public MemorySegment materialize(SegmentAllocator arena) {
50-
return buffer;
51-
}
52-
53-
@Override
54-
public Optional<MemorySegment> segmentIfPresent() {
55-
return Optional.of(buffer);
56-
}
57-
5826
@Override
5927
public float getFloat(long i) {
6028
return buffer.getAtIndex(PTypeIO.LE_FLOAT, length == elementCount ? i : i % elementCount);

reader/src/main/java/io/github/dfa1/vortex/reader/array/MaterializedIntArray.java

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,13 @@
55
import io.github.dfa1.vortex.encoding.PTypeIO;
66

77
import java.lang.foreign.MemorySegment;
8-
import java.lang.foreign.SegmentAllocator;
9-
import java.util.Optional;
108
import java.util.function.IntBinaryOperator;
119
import java.util.function.IntConsumer;
1210

1311
/// Buffer-backed [IntArray] — the fallback used when an encoding decoder
1412
/// either materialises the output eagerly or has no lazy variant of its own.
15-
public final class MaterializedIntArray implements IntArray {
13+
public final class MaterializedIntArray extends AbstractMaterializedArray implements IntArray {
1614

17-
private final DType dtype;
18-
private final long length;
19-
private final MemorySegment buffer;
2015
private final long elementCount;
2116

2217
/// Creates a new `MaterializedIntArray` backed by the given memory segment.
@@ -25,37 +20,10 @@ public final class MaterializedIntArray implements IntArray {
2520
/// @param length number of elements
2621
/// @param buffer little-endian int data (4 bytes per element)
2722
public MaterializedIntArray(DType dtype, long length, MemorySegment buffer) {
28-
this.dtype = dtype;
29-
this.length = length;
30-
this.buffer = buffer;
23+
super(dtype, length, buffer);
3124
this.elementCount = buffer.byteSize() / PTypeIO.LE_INT.byteSize();
3225
}
3326

34-
@Override
35-
public DType dtype() {
36-
return dtype;
37-
}
38-
39-
@Override
40-
public long length() {
41-
return length;
42-
}
43-
44-
/// Returns the backing buffer directly — already a contiguous little-endian
45-
/// `i32` segment, so no copy or allocation is needed.
46-
///
47-
/// @param arena unused; the existing buffer is returned as-is
48-
/// @return the backing little-endian `i32` segment
49-
@Override
50-
public MemorySegment materialize(SegmentAllocator arena) {
51-
return buffer;
52-
}
53-
54-
@Override
55-
public Optional<MemorySegment> segmentIfPresent() {
56-
return Optional.of(buffer);
57-
}
58-
5927
@Override
6028
public int getInt(long i) {
6129
return buffer.getAtIndex(PTypeIO.LE_INT, length == elementCount ? i : i % elementCount);

0 commit comments

Comments
 (0)