Skip to content

Commit 2d10c73

Browse files
dfa1claude
andcommitted
perf: slab allocator + in-place codec conversion
- DecodeContext/CodecRegistry: Arena→SegmentAllocator so callers can pass a slicing (bump-pointer) allocator instead of the global Arena - ScanIterator: pre-allocate 4 MB chunkSlab per trial; create a fresh SegmentAllocator.slicingAllocator per chunk to eliminate per-chunk native malloc (was 25% of CPU in volume benchmark) - BitpackedCodec/DeltaCodec/FOR/Sparse/Sequence: remove asReadOnly() from decode output so AlpCodec can convert in-place when the buffer is writable (avoids extra allocation in ALP+bitpacked chain) - AlpCodec: check isReadOnly() before in-place F64/F32 conversion; fall back to arena.allocate() for read-only mmap slices Result: javaReadVolume 118→145 ops/s (+23%), javaReadClose 59→86 ops/s (+46%) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2786a44 commit 2d10c73

10 files changed

Lines changed: 79 additions & 63 deletions

File tree

core/src/main/java/io/github/dfa1/vortex/encoding/AlpCodec.java

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -262,16 +262,24 @@ private Array decodeF64(DecodeContext ctx, EncodingProtos.ALPMetadata meta, int
262262
double factor = F10_F64[expF] * IF10_F64[expE];
263263

264264
MemorySegment src = encoded.buffer(0);
265-
MemorySegment out = ctx.arena().allocate(n * 8, 8);
266-
for (long i = 0; i < n; i++) {
267-
out.setAtIndex(LE_DOUBLE, i, (double) src.getAtIndex(LE_LONG, i) * factor);
265+
// In-place when the child returned a writable arena buffer (e.g. BitpackedCodec, DeltaCodec).
266+
// Fall back to a new allocation when the source is a read-only mmap slice (PrimitiveCodec).
267+
MemorySegment buf = src.isReadOnly() ? ctx.arena().allocate(n * 8, 8) : src;
268+
if (src.isReadOnly()) {
269+
for (long i = 0; i < n; i++) {
270+
buf.setAtIndex(LE_DOUBLE, i, (double) src.getAtIndex(LE_LONG, i) * factor);
271+
}
272+
} else {
273+
for (long i = 0; i < n; i++) {
274+
buf.setAtIndex(LE_DOUBLE, i, (double) buf.getAtIndex(LE_LONG, i) * factor);
275+
}
268276
}
269277

270278
if (meta.hasPatches()) {
271-
applyPatches(ctx, meta.getPatches(), out, LE_LONG, 8);
279+
applyPatches(ctx, meta.getPatches(), buf, LE_LONG, 8);
272280
}
273281

274-
return new DoubleArray(ctx.dtype(), n, out.asReadOnly(), ArrayStats.empty());
282+
return new DoubleArray(ctx.dtype(), n, buf.asReadOnly(), ArrayStats.empty());
275283
}
276284

277285
private Array decodeF32(DecodeContext ctx, EncodingProtos.ALPMetadata meta, int expE, int expF, long n) {
@@ -280,17 +288,23 @@ private Array decodeF32(DecodeContext ctx, EncodingProtos.ALPMetadata meta, int
280288
// Precompute single factor — avoids 2 FP mults per element in the hot loop.
281289
float factor = F10_F32[expF] * IF10_F32[expE];
282290

283-
MemorySegment src = encoded.buffer(0);
284-
MemorySegment out = ctx.arena().allocate(n * 4, 4);
285-
for (long i = 0; i < n; i++) {
286-
out.setAtIndex(LE_FLOAT, i, (float) src.getAtIndex(LE_INT, i) * factor);
291+
MemorySegment src32 = encoded.buffer(0);
292+
MemorySegment buf32 = src32.isReadOnly() ? ctx.arena().allocate(n * 4, 4) : src32;
293+
if (src32.isReadOnly()) {
294+
for (long i = 0; i < n; i++) {
295+
buf32.setAtIndex(LE_FLOAT, i, (float) src32.getAtIndex(LE_INT, i) * factor);
296+
}
297+
} else {
298+
for (long i = 0; i < n; i++) {
299+
buf32.setAtIndex(LE_FLOAT, i, (float) buf32.getAtIndex(LE_INT, i) * factor);
300+
}
287301
}
288302

289303
if (meta.hasPatches()) {
290-
applyPatches(ctx, meta.getPatches(), out, LE_INT, 4);
304+
applyPatches(ctx, meta.getPatches(), buf32, LE_INT, 4);
291305
}
292306

293-
return new FloatArray(ctx.dtype(), n, out.asReadOnly(), ArrayStats.empty());
307+
return new FloatArray(ctx.dtype(), n, buf32.asReadOnly(), ArrayStats.empty());
294308
}
295309

296310
private void applyPatches(DecodeContext ctx, EncodingProtos.PatchesMetadata pm,

core/src/main/java/io/github/dfa1/vortex/encoding/BitpackedCodec.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -588,12 +588,11 @@ public Array decode(DecodeContext ctx) {
588588
applyPatches(ctx, meta.getPatches(), output, ptype.byteSize());
589589
}
590590

591-
MemorySegment ro = output.asReadOnly();
592591
return switch (ptype) {
593-
case I64, U64 -> new LongArray(ctx.dtype(), rowCount, ro, ArrayStats.empty());
594-
case I32, U32 -> new IntArray(ctx.dtype(), rowCount, ro, ArrayStats.empty());
595-
case I16, U16 -> new ShortArray(ctx.dtype(), rowCount, ro, ArrayStats.empty());
596-
case I8, U8 -> new ByteArray(ctx.dtype(), rowCount, ro, ArrayStats.empty());
592+
case I64, U64 -> new LongArray(ctx.dtype(), rowCount, output, ArrayStats.empty());
593+
case I32, U32 -> new IntArray(ctx.dtype(), rowCount, output, ArrayStats.empty());
594+
case I16, U16 -> new ShortArray(ctx.dtype(), rowCount, output, ArrayStats.empty());
595+
case I8, U8 -> new ByteArray(ctx.dtype(), rowCount, output, ArrayStats.empty());
597596
default -> throw new VortexException(CodecId.FASTLANES_BITPACKED, "unsupported ptype " + ptype);
598597
};
599598
}

core/src/main/java/io/github/dfa1/vortex/encoding/CodecRegistry.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import io.github.dfa1.vortex.core.VortexException;
77
import io.github.dfa1.vortex.fbs.Buffer;
88

9-
import java.lang.foreign.Arena;
109
import java.lang.foreign.MemorySegment;
10+
import java.lang.foreign.SegmentAllocator;
1111
import java.nio.ByteBuffer;
1212
import java.nio.ByteOrder;
1313
import java.util.HashMap;
@@ -67,7 +67,7 @@ public void register(Codec codec) {
6767
///
6868
/// Segment format: [bufferdata...] [FlatBufferArray] [4-byteLEu32=FlatBuffersize].
6969
public Array decodeSegment(MemorySegment seg, List<String> encodingSpecs,
70-
DType dtype, long rowCount, Arena arena) {
70+
DType dtype, long rowCount, SegmentAllocator arena) {
7171
int segLen = (int) seg.byteSize();
7272
ByteBuffer bb = seg.asByteBuffer().order(ByteOrder.LITTLE_ENDIAN);
7373

core/src/main/java/io/github/dfa1/vortex/encoding/DecodeContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import io.github.dfa1.vortex.core.array.Array;
44
import io.github.dfa1.vortex.core.DType;
55

6-
import java.lang.foreign.Arena;
76
import java.lang.foreign.MemorySegment;
7+
import java.lang.foreign.SegmentAllocator;
88
import java.nio.ByteBuffer;
99

1010
/// Decoding context passed to each [Codec].
@@ -19,7 +19,7 @@ public record DecodeContext(
1919
long rowCount,
2020
MemorySegment[] segmentBuffers,
2121
CodecRegistry registry,
22-
Arena arena
22+
SegmentAllocator arena
2323
) {
2424
/// Recursively decode child `i` using the same segment buffers, registry and arena.
2525
public Array decodeChild(int i) {

core/src/main/java/io/github/dfa1/vortex/encoding/DeltaCodec.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import io.github.dfa1.vortex.core.array.ShortArray;
1212
import io.github.dfa1.vortex.core.VortexException;
1313

14-
import java.lang.foreign.Arena;
14+
import java.lang.foreign.SegmentAllocator;
1515
import java.lang.foreign.MemorySegment;
1616
import java.lang.foreign.ValueLayout;
1717
import java.nio.ByteBuffer;
@@ -119,13 +119,13 @@ private static long[] toLongs(Object data, PType ptype) {
119119

120120
// ── Decode ────────────────────────────────────────────────────────────────
121121

122-
private static MemorySegment fromLongs(long[] longs, PType ptype, Arena arena) {
122+
private static MemorySegment fromLongs(long[] longs, PType ptype, SegmentAllocator arena) {
123123
// Fast path: 64-bit target — bulk byte-order copy, no per-element narrowing.
124124
if (ptype == PType.I64 || ptype == PType.U64) {
125125
MemorySegment dst = arena.allocate((long) longs.length * 8);
126126
MemorySegment.copy(MemorySegment.ofArray(longs), ValueLayout.JAVA_LONG, 0L,
127127
dst, PTypeIO.valueLayout(ptype), 0L, longs.length);
128-
return dst.asReadOnly();
128+
return dst;
129129
}
130130
// Narrowing path: per-element MethodHandle setter from PTypeIO.
131131
int n = longs.length;
@@ -134,7 +134,7 @@ private static MemorySegment fromLongs(long[] longs, PType ptype, Arena arena) {
134134
for (int i = 0; i < n; i++) {
135135
PTypeIO.set(seg, i * elemSize, ptype, longs[i]);
136136
}
137-
return seg.asReadOnly();
137+
return seg;
138138
}
139139

140140
// ── Bit packing ───────────────────────────────────────────────────────────

core/src/main/java/io/github/dfa1/vortex/encoding/FrameOfReferenceCodec.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
import io.github.dfa1.vortex.core.array.ShortArray;
1414
import io.github.dfa1.vortex.core.VortexException;
1515

16-
import java.lang.foreign.Arena;
1716
import java.lang.foreign.MemorySegment;
17+
import java.lang.foreign.SegmentAllocator;
1818
import java.lang.foreign.ValueLayout;
1919
import java.nio.ByteBuffer;
2020
import java.nio.ByteOrder;
@@ -40,7 +40,7 @@ private static long referenceValue(ScalarProtos.ScalarValue scalar) {
4040
};
4141
}
4242

43-
private static MemorySegment applyReference(MemorySegment src, long n, PType ptype, long ref, Arena arena) {
43+
private static MemorySegment applyReference(MemorySegment src, long n, PType ptype, long ref, SegmentAllocator arena) {
4444
int wordBytes = ptype.byteSize();
4545
MemorySegment dst = arena.allocate(n * wordBytes);
4646
switch (ptype) {
@@ -111,13 +111,12 @@ public Array decode(DecodeContext ctx) {
111111
MemorySegment src = encoded.buffer(0);
112112
long n = ctx.rowCount();
113113
MemorySegment dst = applyReference(src, n, p.ptype(), ref, ctx.arena());
114-
MemorySegment ro = dst.asReadOnly();
115114
return switch (p.ptype()) {
116-
case I64, U64 -> new LongArray(ctx.dtype(), n, ro, ArrayStats.empty());
117-
case I32, U32 -> new IntArray(ctx.dtype(), n, ro, ArrayStats.empty());
118-
case F64 -> new DoubleArray(ctx.dtype(), n, ro, ArrayStats.empty());
119-
case I16, U16 -> new ShortArray(ctx.dtype(), n, ro, ArrayStats.empty());
120-
case I8, U8 -> new ByteArray(ctx.dtype(), n, ro, ArrayStats.empty());
115+
case I64, U64 -> new LongArray(ctx.dtype(), n, dst, ArrayStats.empty());
116+
case I32, U32 -> new IntArray(ctx.dtype(), n, dst, ArrayStats.empty());
117+
case F64 -> new DoubleArray(ctx.dtype(), n, dst, ArrayStats.empty());
118+
case I16, U16 -> new ShortArray(ctx.dtype(), n, dst, ArrayStats.empty());
119+
case I8, U8 -> new ByteArray(ctx.dtype(), n, dst, ArrayStats.empty());
121120
default -> throw new VortexException(CodecId.FASTLANES_FOR, "unsupported ptype " + p.ptype());
122121
};
123122
}

core/src/main/java/io/github/dfa1/vortex/encoding/RunEndCodec.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import io.github.dfa1.vortex.core.array.VarBinArray;
1616
import io.github.dfa1.vortex.core.VortexException;
1717

18-
import java.lang.foreign.Arena;
18+
import java.lang.foreign.SegmentAllocator;
1919
import java.lang.foreign.MemorySegment;
2020
import java.lang.foreign.ValueLayout;
2121
import java.nio.ByteBuffer;
@@ -41,7 +41,7 @@ private static Array expand(
4141
MemorySegment endsSeg, MemorySegment valuesSeg,
4242
PType endsPtype, PType valuePtype,
4343
long numRuns, long offset, long n,
44-
DType dtype, Arena arena
44+
DType dtype, SegmentAllocator arena
4545
) {
4646
MemorySegment out = arena.allocate(n * valuePtype.byteSize());
4747
switch (valuePtype) {
@@ -139,7 +139,7 @@ private static long readUnsigned(MemorySegment seg, long i, PType ptype) {
139139
private static Array expandBool(
140140
Array endsArr, BoolArray valuesArr,
141141
PType endsPtype, long numRuns, long offset, long n,
142-
DType dtype, Arena arena
142+
DType dtype, SegmentAllocator arena
143143
) {
144144
MemorySegment endsSeg = endsArr.buffer(0);
145145
long numBytes = (n + 7) >>> 3;
@@ -167,7 +167,7 @@ private static Array expandBool(
167167
private static Array expandStrings(
168168
Array endsArr, VarBinArray valuesArr,
169169
PType endsPtype, long numRuns, long offset, long n,
170-
DType dtype, Arena arena
170+
DType dtype, SegmentAllocator arena
171171
) {
172172
MemorySegment endsSeg = endsArr.buffer(0);
173173
MemorySegment valBytes = valuesArr.buffer(0);

core/src/main/java/io/github/dfa1/vortex/encoding/SequenceCodec.java

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import io.github.dfa1.vortex.core.array.ShortArray;
1515
import io.github.dfa1.vortex.core.VortexException;
1616

17-
import java.lang.foreign.Arena;
17+
import java.lang.foreign.SegmentAllocator;
1818
import java.lang.foreign.MemorySegment;
1919
import java.lang.foreign.ValueLayout;
2020
import java.nio.ByteBuffer;
@@ -34,7 +34,7 @@ public final class SequenceCodec implements Codec {
3434
private static final ValueLayout.OfFloat LE_FLOAT = ValueLayout.JAVA_FLOAT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN);
3535

3636
private static Array decodeInteger(
37-
EncodingProtos.SequenceMetadata meta, PType pt, long n, DType dtype, Arena arena
37+
EncodingProtos.SequenceMetadata meta, PType pt, long n, DType dtype, SegmentAllocator arena
3838
) {
3939
long base = signedValue(meta.getBase());
4040
long mul = signedValue(meta.getMultiplier());
@@ -50,34 +50,33 @@ private static Array decodeInteger(
5050
default -> throw new IllegalStateException("unreachable");
5151
}
5252
}
53-
MemorySegment ro = seg.asReadOnly();
5453
return switch (pt) {
55-
case I64, U64 -> new LongArray(dtype, n, ro, ArrayStats.empty());
56-
case I32, U32 -> new IntArray(dtype, n, ro, ArrayStats.empty());
57-
case I16, U16 -> new ShortArray(dtype, n, ro, ArrayStats.empty());
58-
case I8, U8 -> new ByteArray(dtype, n, ro, ArrayStats.empty());
54+
case I64, U64 -> new LongArray(dtype, n, seg, ArrayStats.empty());
55+
case I32, U32 -> new IntArray(dtype, n, seg, ArrayStats.empty());
56+
case I16, U16 -> new ShortArray(dtype, n, seg, ArrayStats.empty());
57+
case I8, U8 -> new ByteArray(dtype, n, seg, ArrayStats.empty());
5958
default -> throw new VortexException(CodecId.VORTEX_SEQUENCE, "unsupported ptype " + pt);
6059
};
6160
}
6261

63-
private static Array decodeF32(EncodingProtos.SequenceMetadata meta, long n, DType dtype, Arena arena) {
62+
private static Array decodeF32(EncodingProtos.SequenceMetadata meta, long n, DType dtype, SegmentAllocator arena) {
6463
float base = meta.getBase().getF32Value();
6564
float mul = meta.getMultiplier().getF32Value();
6665
MemorySegment seg = arena.allocate(n * 4L);
6766
for (long i = 0; i < n; i++) {
6867
seg.setAtIndex(LE_FLOAT, i, base + i * mul);
6968
}
70-
return new FloatArray(dtype, n, seg.asReadOnly(), ArrayStats.empty());
69+
return new FloatArray(dtype, n, seg, ArrayStats.empty());
7170
}
7271

73-
private static Array decodeF64(EncodingProtos.SequenceMetadata meta, long n, DType dtype, Arena arena) {
72+
private static Array decodeF64(EncodingProtos.SequenceMetadata meta, long n, DType dtype, SegmentAllocator arena) {
7473
double base = meta.getBase().getF64Value();
7574
double mul = meta.getMultiplier().getF64Value();
7675
MemorySegment seg = arena.allocate(n * 8L);
7776
for (long i = 0; i < n; i++) {
7877
seg.setAtIndex(LE_DOUBLE, i, base + i * mul);
7978
}
80-
return new DoubleArray(dtype, n, seg.asReadOnly(), ArrayStats.empty());
79+
return new DoubleArray(dtype, n, seg, ArrayStats.empty());
8180
}
8281

8382
private static long signedValue(dev.vortex.proto.ScalarProtos.ScalarValue sv) {

core/src/main/java/io/github/dfa1/vortex/encoding/SparseCodec.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,13 @@ public Array decode(DecodeContext ctx) {
182182
indicesArray.buffer(0), valuesArray.buffer(0), indicesPtype, numPatches, offset);
183183
}
184184

185-
MemorySegment ro = out.asReadOnly();
186185
return switch (valuePtype) {
187-
case I64, U64 -> new LongArray(ctx.dtype(), n, ro, ArrayStats.empty());
188-
case I32, U32 -> new IntArray(ctx.dtype(), n, ro, ArrayStats.empty());
189-
case F64 -> new DoubleArray(ctx.dtype(), n, ro, ArrayStats.empty());
190-
case F32 -> new FloatArray(ctx.dtype(), n, ro, ArrayStats.empty());
191-
case I16, U16 -> new ShortArray(ctx.dtype(), n, ro, ArrayStats.empty());
192-
case I8, U8 -> new ByteArray(ctx.dtype(), n, ro, ArrayStats.empty());
186+
case I64, U64 -> new LongArray(ctx.dtype(), n, out, ArrayStats.empty());
187+
case I32, U32 -> new IntArray(ctx.dtype(), n, out, ArrayStats.empty());
188+
case F64 -> new DoubleArray(ctx.dtype(), n, out, ArrayStats.empty());
189+
case F32 -> new FloatArray(ctx.dtype(), n, out, ArrayStats.empty());
190+
case I16, U16 -> new ShortArray(ctx.dtype(), n, out, ArrayStats.empty());
191+
case I8, U8 -> new ByteArray(ctx.dtype(), n, out, ArrayStats.empty());
193192
default -> throw new VortexException(CodecId.VORTEX_SPARSE, "unsupported ptype " + valuePtype);
194193
};
195194
}

0 commit comments

Comments
 (0)