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 ab36c9aa perf(parquet): reuse fixed-length zero scratch (#1236)
ab36c9aa is described below
commit ab36c9aada052e8ba05c146cf12b4ca24f4780ed
Author: Minh Vu <[email protected]>
AuthorDate: Fri Aug 28 17:59:07 2026 +0200
perf(parquet): reuse fixed-length zero scratch (#1236)
## Summary
- Reuse a zero-value buffer in `PlainFixedLenByteArrayEncoder`.
- Allocate it lazily only when `Put` sees a nil value.
- Clear the buffer reference when the encoder is released.
- Add coverage for lazy creation and reuse.
- Add benchmarks for widths 4, 16, and 32 with repeated small `Put`
calls.
## Benchmark
Apple M1 Pro. Median of 3 runs. All-valid input.
| width | rows per put | puts | upstream main | this PR | change |
| ---: | ---: | ---: | ---: | ---: | ---: |
| 4 | 1 | 64 | 489 ns | 470 ns | -4% |
| 16 | 1 | 64 | 516 ns | 470 ns | -9% |
| 32 | 1 | 64 | 498 ns | 471 ns | -5% |
The benchmark also covers 1, 16, and 1024 rows with 1, 64, and 1024
repeated puts, plus sparse nil input.
```text
go test ./parquet/internal/encoding -run '^$' -bench
'BenchmarkPlainEncodingFixedLenByteArray' -benchmem -benchtime=100ms -count=3
```
## Tests
- `go test ./parquet/internal/encoding -count=1`
- `PARQUET_TEST_DATA=/path/to/parquet-testing/data go test
./parquet/file ./parquet/pqarrow -count=1`
- `PARQUET_TEST_DATA=/path/to/parquet-testing/data go test -race
./parquet/internal/encoding ./parquet/file ./parquet/pqarrow -count=1`
- `go vet ./parquet/internal/encoding ./parquet/file ./parquet/pqarrow`
---
.../encoding/fixed_len_byte_array_encoder.go | 13 +++-
.../fixed_len_byte_array_encoder_benchmark_test.go | 80 ++++++++++++++++++++++
.../encoding/fixed_len_byte_array_encoder_test.go | 24 +++++++
3 files changed, 114 insertions(+), 3 deletions(-)
diff --git a/parquet/internal/encoding/fixed_len_byte_array_encoder.go
b/parquet/internal/encoding/fixed_len_byte_array_encoder.go
index 854b8b8a..190802ee 100644
--- a/parquet/internal/encoding/fixed_len_byte_array_encoder.go
+++ b/parquet/internal/encoding/fixed_len_byte_array_encoder.go
@@ -31,6 +31,7 @@ type PlainFixedLenByteArrayEncoder struct {
encoder
bitSetReader bitutils.SetBitRunReader
+ zeroValue []byte
}
// Put writes the provided values to the encoder
@@ -43,17 +44,23 @@ func (enc *PlainFixedLenByteArrayEncoder) Put(in
[]parquet.FixedLenByteArray) {
bytesNeeded := len(in) * typeLen
enc.sink.Reserve(bytesNeeded)
- emptyValue := make([]byte, typeLen)
-
for _, val := range in {
if val == nil {
- enc.sink.UnsafeWrite(emptyValue)
+ if len(enc.zeroValue) != typeLen {
+ enc.zeroValue = make([]byte, typeLen)
+ }
+ enc.sink.UnsafeWrite(enc.zeroValue)
} else {
enc.sink.UnsafeWrite(val[:typeLen])
}
}
}
+func (enc *PlainFixedLenByteArrayEncoder) Release() {
+ enc.encoder.Release()
+ enc.zeroValue = nil
+}
+
// PutSpaced is like Put but works with data that is spaced out according to
the passed in bitmap
func (enc *PlainFixedLenByteArrayEncoder) PutSpaced(in
[]parquet.FixedLenByteArray, validBits []byte, validBitsOffset int64) {
if validBits != nil {
diff --git
a/parquet/internal/encoding/fixed_len_byte_array_encoder_benchmark_test.go
b/parquet/internal/encoding/fixed_len_byte_array_encoder_benchmark_test.go
new file mode 100644
index 00000000..d4ea9186
--- /dev/null
+++ b/parquet/internal/encoding/fixed_len_byte_array_encoder_benchmark_test.go
@@ -0,0 +1,80 @@
+// 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/memory"
+ "github.com/apache/arrow-go/v18/parquet"
+ "github.com/apache/arrow-go/v18/parquet/internal/encoding"
+ "github.com/apache/arrow-go/v18/parquet/schema"
+)
+
+func BenchmarkPlainEncodingFixedLenByteArray(b *testing.B) {
+ for _, width := range []int{4, 16, 32} {
+ for _, nvalues := range []int{1, 16, 1024} {
+ for _, puts := range []int{1, 64, 1024} {
+ for _, withNulls := range []bool{false, true} {
+ validity := "all-valid"
+ if withNulls {
+ validity = "sparse-nil"
+ }
+ name :=
fmt.Sprintf("width=%d/rows=%d/puts=%d/validity=%s", width, nvalues, puts,
validity)
+ values :=
makeFixedLenByteArrayValues(nvalues, width, withNulls)
+
+ b.Run(name, func(b *testing.B) {
+ col :=
schema.NewColumn(schema.NewFixedLenByteArrayNode("fixedlenbytearray",
parquet.Repetitions.Required, int32(width), -1), 0, 0)
+ encoder :=
encoding.NewEncoder(parquet.Types.FixedLenByteArray, parquet.Encodings.Plain,
+ false, col,
memory.DefaultAllocator).(encoding.FixedLenByteArrayEncoder)
+ defer encoder.Release()
+
+ b.SetBytes(int64(width *
nvalues * puts))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ for j := 0; j < puts;
j++ {
+
encoder.Put(values)
+ }
+ buf, err :=
encoder.FlushValues()
+ if err != nil {
+ b.Fatal(err)
+ }
+ buf.Release()
+ }
+ })
+ }
+ }
+ }
+ }
+}
+
+func makeFixedLenByteArrayValues(nvalues, width int, withNulls bool)
[]parquet.FixedLenByteArray {
+ values := make([]parquet.FixedLenByteArray, nvalues)
+ for i := range values {
+ if withNulls && i%8 == 0 {
+ continue
+ }
+
+ values[i] = make(parquet.FixedLenByteArray, width)
+ for j := range values[i] {
+ values[i][j] = byte(j)
+ }
+ }
+ return values
+}
diff --git a/parquet/internal/encoding/fixed_len_byte_array_encoder_test.go
b/parquet/internal/encoding/fixed_len_byte_array_encoder_test.go
index 67e83b02..1edee315 100644
--- a/parquet/internal/encoding/fixed_len_byte_array_encoder_test.go
+++ b/parquet/internal/encoding/fixed_len_byte_array_encoder_test.go
@@ -78,3 +78,27 @@ func TestPlainFixedLenByteArrayEncoder_Put(t *testing.T) {
})
}
}
+
+func TestPlainFixedLenByteArrayEncoder_ReusesZeroValue(t *testing.T) {
+ sink := NewPooledBufferWriter(0)
+ elem := schema.NewFixedLenByteArrayNode("test",
parquet.Repetitions.Required, 4, 0)
+ descr := schema.NewColumn(elem, 0, 0)
+ encoder := &PlainFixedLenByteArrayEncoder{
+ encoder: encoder{
+ descr: descr,
+ sink: sink,
+ },
+ }
+ defer encoder.Release()
+
+ encoder.Put([]parquet.FixedLenByteArray{[]byte("abcd")})
+ require.Nil(t, encoder.zeroValue)
+ sink.Reset(0)
+ encoder.Put([]parquet.FixedLenByteArray{nil})
+ zeroValue := encoder.zeroValue
+ require.Equal(t, []byte{0, 0, 0, 0}, zeroValue)
+
+ sink.Reset(0)
+ encoder.Put([]parquet.FixedLenByteArray{nil})
+ require.Same(t, &zeroValue[0], &encoder.zeroValue[0])
+}