Skip to content

Commit 30c70de

Browse files
dfa1claude
andcommitted
perf: eliminate byte[] alloc in decode path
Replace toArray()/new byte[]+copy patterns with direct ByteBuffer/MemorySegment operations: parseFrom(ByteBuffer), asByteBuffer().put(), and MemorySegment-based unpack in DeltaCodec. Removes heap round-trips in all five codecs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent dc450ee commit 30c70de

5 files changed

Lines changed: 14 additions & 26 deletions

File tree

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,9 @@ public EncodeResult encode(DType dtype, Object data) {
6161
@Override
6262
public Array decode(DecodeContext ctx) {
6363
MemorySegment scalarBuf = ctx.buffer(0);
64-
byte[] scalarBytes = scalarBuf.toArray(ValueLayout.JAVA_BYTE);
65-
6664
ScalarProtos.ScalarValue scalar;
6765
try {
68-
scalar = ScalarProtos.ScalarValue.parseFrom(scalarBytes);
66+
scalar = ScalarProtos.ScalarValue.parseFrom(scalarBuf.asByteBuffer());
6967
} catch (InvalidProtocolBufferException e) {
7068
throw new VortexException(CodecId.VORTEX_CONSTANT, "invalid scalar value", e);
7169
}
@@ -104,7 +102,7 @@ private static Array decodeString(DecodeContext ctx, ScalarProtos.ScalarValue sc
104102
// Store the string bytes once; all n offsets point into the same [0, strLen] range.
105103
// Offsets layout: [0, strLen, 0, strLen, ...] — each pair encodes the same slice.
106104
MemorySegment bytesSeg = ctx.arena().allocate(strLen);
107-
MemorySegment.copy(MemorySegment.ofArray(strBytes), 0, bytesSeg, 0, strLen);
105+
bytesSeg.asByteBuffer().put(strBytes);
108106

109107
// Offsets: n+1 I32 values, alternating start/end of the single string.
110108
MemorySegment offsetsSeg = ctx.arena().allocate((n + 1) * 4L, 4);

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,16 @@ private static ByteBuffer pack(long[] values, long frameOfRef, int bitWidth) {
3838
return ByteBuffer.wrap(buf);
3939
}
4040

41-
private static long[] unpack(byte[] packed, int count, int bitWidth, long frameOfRef) {
41+
private static long[] unpack(MemorySegment packed, int count, int bitWidth, long frameOfRef) {
4242
long[] out = new long[count];
43+
long size = packed.byteSize();
4344
for (int i = 0; i < count; i++) {
4445
long val = 0L;
4546
int bitPos = i * bitWidth;
4647
for (int b = 0; b < bitWidth; b++) {
4748
int byteIdx = (bitPos + b) >>> 3;
4849
int bitIdx = (bitPos + b) & 7;
49-
if (byteIdx < packed.length && ((packed[byteIdx] >>> bitIdx) & 1) == 1) {
50+
if (byteIdx < size && ((packed.get(ValueLayout.JAVA_BYTE, byteIdx) >>> bitIdx) & 1) == 1) {
5051
val |= 1L << b;
5152
}
5253
}
@@ -238,13 +239,11 @@ public Array decode(DecodeContext ctx) {
238239
long rowCount = ctx.rowCount();
239240

240241
MemorySegment packed = ctx.buffer(0);
241-
byte[] packedBytes = new byte[(int) packed.byteSize()];
242-
MemorySegment.copy(packed, ValueLayout.JAVA_BYTE, 0, packedBytes, 0, packedBytes.length);
243242

244243
long[] longs = new long[(int) rowCount];
245244
if (rowCount > 0) {
246245
longs[0] = baseValue;
247-
long[] deltas = unpack(packedBytes, (int) rowCount - 1, bitWidth, deltaFor);
246+
long[] deltas = unpack(packed, (int) rowCount - 1, bitWidth, deltaFor);
248247
for (int i = 0; i < deltas.length; i++) {
249248
longs[i + 1] = longs[i] + deltas[i];
250249
}

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -237,14 +237,12 @@ public Array decode(DecodeContext ctx) {
237237
}
238238

239239
ByteBuffer meta = ctx.metadata();
240-
byte[] metaBytes = new byte[meta.remaining()];
241-
meta.duplicate().get(metaBytes);
242240

243241
// 1-byte = legacy Java format; multi-byte = Rust proto format
244-
if (metaBytes.length == 1) {
245-
return decodeLegacyJava(ctx, metaBytes[0]);
242+
if (meta.remaining() == 1) {
243+
return decodeLegacyJava(ctx, meta.get(0));
246244
}
247-
return decodeRustProto(ctx, metaBytes);
245+
return decodeRustProto(ctx, meta.duplicate());
248246
}
249247

250248
private Array decodeLegacyJava(DecodeContext ctx, byte codeTypeByte) {
@@ -265,10 +263,10 @@ private Array decodeLegacyJava(DecodeContext ctx, byte codeTypeByte) {
265263
return new Array(ctx.dtype(), rowCount, new MemorySegment[]{out.asReadOnly()}, Array.NO_CHILDREN, ArrayStats.empty());
266264
}
267265

268-
private Array decodeRustProto(DecodeContext ctx, byte[] metaBytes) {
266+
private Array decodeRustProto(DecodeContext ctx, ByteBuffer metaBuf) {
269267
EncodingProtos.DictMetadata meta;
270268
try {
271-
meta = EncodingProtos.DictMetadata.parseFrom(metaBytes);
269+
meta = EncodingProtos.DictMetadata.parseFrom(metaBuf);
272270
} catch (InvalidProtocolBufferException e) {
273271
throw new VortexException(CodecId.VORTEX_DICT, "invalid proto metadata", e);
274272
}

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,9 @@ public Array decode(DecodeContext ctx) {
9090
if (metaBuf == null || !metaBuf.hasRemaining()) {
9191
throw new VortexException(CodecId.VORTEX_SEQUENCE, "missing metadata");
9292
}
93-
byte[] metaBytes = new byte[metaBuf.remaining()];
94-
metaBuf.duplicate().get(metaBytes);
95-
9693
EncodingProtos.SequenceMetadata meta;
9794
try {
98-
meta = EncodingProtos.SequenceMetadata.parseFrom(metaBytes);
95+
meta = EncodingProtos.SequenceMetadata.parseFrom(metaBuf.duplicate());
9996
} catch (InvalidProtocolBufferException e) {
10097
throw new VortexException(CodecId.VORTEX_SEQUENCE, "invalid metadata", e);
10198
}

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,9 @@ public Array decode(DecodeContext ctx) {
135135
if (rawMeta == null || !rawMeta.hasRemaining()) {
136136
throw new VortexException(CodecId.VORTEX_SPARSE, "missing metadata");
137137
}
138-
byte[] metaBytes = new byte[rawMeta.remaining()];
139-
rawMeta.duplicate().get(metaBytes);
140-
141138
EncodingProtos.SparseMetadata sparseMeta;
142139
try {
143-
sparseMeta = EncodingProtos.SparseMetadata.parseFrom(metaBytes);
140+
sparseMeta = EncodingProtos.SparseMetadata.parseFrom(rawMeta.duplicate());
144141
} catch (InvalidProtocolBufferException e) {
145142
throw new VortexException(CodecId.VORTEX_SPARSE, "invalid metadata", e);
146143
}
@@ -157,10 +154,9 @@ public Array decode(DecodeContext ctx) {
157154

158155
// Fill value from buffer[0]
159156
MemorySegment fillBuf = ctx.buffer(0);
160-
byte[] fillBytes = fillBuf.toArray(ValueLayout.JAVA_BYTE);
161157
ScalarProtos.ScalarValue fillScalar;
162158
try {
163-
fillScalar = ScalarProtos.ScalarValue.parseFrom(fillBytes);
159+
fillScalar = ScalarProtos.ScalarValue.parseFrom(fillBuf.asByteBuffer());
164160
} catch (InvalidProtocolBufferException e) {
165161
throw new VortexException(CodecId.VORTEX_SPARSE, "invalid fill value", e);
166162
}

0 commit comments

Comments
 (0)