This is an automated email from the ASF dual-hosted git repository.
zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git
The following commit(s) were added to refs/heads/main by this push:
new a958ec39 perf(parquet): reuse delta PutSpaced scratch (#1230)
a958ec39 is described below
commit a958ec3963508b0fc4e56d152bab3ec2d45f333a
Author: Minh Vu <[email protected]>
AuthorDate: Fri Aug 28 19:43:17 2026 +0200
perf(parquet): reuse delta PutSpaced scratch (#1230)
## What
- Reuse an allocator-backed scratch buffer in
`deltaBitPackEncoder.PutSpaced`.
- Keep the buffer capacity between calls and release it with the
encoder.
- Keep the existing compaction path unchanged.
## Benchmark
Apple M1 Pro, Go 1.26.3. Steady-state repeated `PutSpaced` plus
`FlushValues` calls on the same encoder, with 10% nulls. Scratch growth
is done before timing.
Command: `go test -vet=off ./parquet/internal/encoding -run "^$" -bench
"^BenchmarkDeltaBinaryPackedPutSpaced(Int32|Int64)$" -benchmem
-benchtime=300ms -count=3 -cpu=1`
| Type | Length | Before | After |
| --- | ---: | ---: | ---: |
| INT32 | 1,024 | 5,333 B/op, 36 allocs/op | 469 B/op, 35 allocs/op |
| INT32 | 65,536 | 282,188 B/op, 1,851 allocs/op | 11,836 B/op, 1,850
allocs/op |
| INT64 | 1,024 | 9,941 B/op, 36 allocs/op | 469 B/op, 35 allocs/op |
| INT64 | 65,536 | 544,346 B/op, 1,851 allocs/op | 11,836 B/op, 1,850
allocs/op |
## Tests
- `go test ./parquet/internal/encoding -count=1`
- `PARQUET_TEST_DATA=parquet-testing/data go test ./... -count=1`
---
parquet/internal/encoding/delta_bit_packing.go | 18 ++++-
.../encoding/delta_bit_packing_benchmark_test.go | 93 ++++++++++++++++++++++
.../encoding/delta_bit_packing_validation_test.go | 9 +++
3 files changed, 116 insertions(+), 4 deletions(-)
diff --git a/parquet/internal/encoding/delta_bit_packing.go
b/parquet/internal/encoding/delta_bit_packing.go
index e8b3a30f..e0fbcbdd 100644
--- a/parquet/internal/encoding/delta_bit_packing.go
+++ b/parquet/internal/encoding/delta_bit_packing.go
@@ -371,6 +371,7 @@ type deltaBitPackEncoder[T int32 | int64] struct {
miniBlockSize uint64
numMiniBlocks uint64
deltas []int64
+ spacedScratch *memory.Buffer
}
// flushBlock flushes out a finished block for writing to the underlying
encoder
@@ -502,15 +503,24 @@ func (enc *deltaBitPackEncoder[T])
EstimatedDataEncodedSize() int64 {
return int64(enc.bitWriter.Written())
}
+func (enc *deltaBitPackEncoder[T]) Release() {
+ enc.encoder.Release()
+ if enc.spacedScratch != nil {
+ enc.spacedScratch.Release()
+ enc.spacedScratch = nil
+ }
+}
+
// PutSpaced takes a slice of values along with a bitmap that describes the
nulls and an offset into the bitmap
// in order to write spaced data to the encoder.
func (enc *deltaBitPackEncoder[T]) PutSpaced(in []T, validBits []byte,
validBitsOffset int64) {
- buffer := memory.NewResizableBuffer(enc.mem)
+ if enc.spacedScratch == nil {
+ enc.spacedScratch = memory.NewResizableBuffer(enc.mem)
+ }
dt := arrow.GetDataType[T]().(arrow.FixedWidthDataType)
- buffer.Reserve(dt.Bytes() * len(in))
- defer buffer.Release()
+ enc.spacedScratch.ResizeNoShrink(dt.Bytes() * len(in))
- data := arrow.GetData[T](buffer.Buf())
+ data := arrow.GetData[T](enc.spacedScratch.Buf())
nvalid := spacedCompress(in, data, validBits, validBitsOffset)
enc.Put(data[:nvalid])
}
diff --git a/parquet/internal/encoding/delta_bit_packing_benchmark_test.go
b/parquet/internal/encoding/delta_bit_packing_benchmark_test.go
new file mode 100644
index 00000000..04c0a65f
--- /dev/null
+++ b/parquet/internal/encoding/delta_bit_packing_benchmark_test.go
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package encoding_test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/apache/arrow-go/v18/arrow"
+ "github.com/apache/arrow-go/v18/arrow/bitutil"
+ "github.com/apache/arrow-go/v18/arrow/memory"
+ "github.com/apache/arrow-go/v18/parquet"
+ "github.com/apache/arrow-go/v18/parquet/internal/encoding"
+)
+
+func BenchmarkDeltaBinaryPackedPutSpacedInt32(b *testing.B) {
+ benchmarkDeltaBinaryPackedPutSpaced[int32](b, parquet.Types.Int32,
arrow.Int32SizeBytes)
+}
+
+func BenchmarkDeltaBinaryPackedPutSpacedInt64(b *testing.B) {
+ benchmarkDeltaBinaryPackedPutSpaced[int64](b, parquet.Types.Int64,
arrow.Int64SizeBytes)
+}
+
+func benchmarkDeltaBinaryPackedPutSpaced[T int32 | int64](b *testing.B, typ
parquet.Type, bytesPerValue int) {
+ patterns := []struct {
+ name string
+ valid func(int) bool
+ }{
+ {name: "all_valid", valid: func(int) bool { return true }},
+ {name: "ten_percent_null", valid: func(i int) bool { return
i%10 != 0 }},
+ {name: "fifty_percent_null", valid: func(i int) bool { return
i%2 != 0 }},
+ {name: "ninety_percent_null", valid: func(i int) bool { return
i%10 == 0 }},
+ }
+
+ for _, length := range []int{1024, 64 * 1024} {
+ b.Run(fmt.Sprintf("length_%d", length), func(b *testing.B) {
+ values := make([]T, length)
+ for i := range values {
+ values[i] = T(i)
+ }
+
+ for _, pattern := range patterns {
+ b.Run(pattern.name, func(b *testing.B) {
+ validBits := make([]byte,
bitutil.BytesForBits(int64(length)))
+ for i := range length {
+ if pattern.valid(i) {
+
bitutil.SetBit(validBits, i)
+ }
+ }
+
+ encoder := encoding.NewEncoder(
+ typ,
parquet.Encodings.DeltaBinaryPacked,
+ false, nil,
memory.DefaultAllocator,
+ ).(encoding.Encoder[T])
+ defer encoder.Release()
+
+ encoder.PutSpaced(values, validBits, 0)
+ buf, err := encoder.FlushValues()
+ if err != nil {
+ b.Fatal(err)
+ }
+ buf.Release()
+
+ b.ReportAllocs()
+ b.SetBytes(int64(length *
bytesPerValue))
+ b.ResetTimer()
+ for b.Loop() {
+ encoder.PutSpaced(values,
validBits, 0)
+ buf, err :=
encoder.FlushValues()
+ if err != nil {
+ b.Fatal(err)
+ }
+ buf.Release()
+ }
+ })
+ }
+ })
+ }
+}
diff --git a/parquet/internal/encoding/delta_bit_packing_validation_test.go
b/parquet/internal/encoding/delta_bit_packing_validation_test.go
index 030aa96a..0f71f804 100644
--- a/parquet/internal/encoding/delta_bit_packing_validation_test.go
+++ b/parquet/internal/encoding/delta_bit_packing_validation_test.go
@@ -117,3 +117,12 @@ func TestDeltaBitPackDecoderBoundsPackedScratch(t
*testing.T) {
require.Error(t, err)
require.LessOrEqual(t, cap(dec.deltaBuf), deltaBitPackScratchSize)
}
+
+func TestDeltaBitPackEncoderReleasesSpacedScratch(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ enc := NewEncoder(parquet.Types.Int32,
parquet.Encodings.DeltaBinaryPacked, false, nil,
mem).(*deltaBitPackEncoder[int32])
+ enc.PutSpaced([]int32{1, 2, 3, 4}, []byte{0x0f}, 0)
+ enc.Release()
+}