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 eda27cd1 perf(parquet): use typed memo insertion for fixed-width
dictionaries (#1312)
eda27cd1 is described below
commit eda27cd162dd2e92ba23be2c0cc00dcbd29eeddc
Author: Minh Vu <[email protected]>
AuthorDate: Tue Sep 15 20:57:56 2026 +0200
perf(parquet): use typed memo insertion for fixed-width dictionaries (#1312)
## What changed
- Use BinaryMemoTable.InsertOrGet for FLBA dictionary Put and
PutDictionary.
- Use the same typed insertion path for DictInt96Encoder.Put.
- Keep empty Put calls as no-ops.
- Add Int96 dictionary round-trip coverage and FLBA/Int96 benchmarks.
## Why
These encoders already use the binary memo table, but still called
GetOrInsert through the generic interface for every value. The typed
entry point avoids the extra interface conversion. Dictionary order and
encoded output stay unchanged.
## Benchmark
Apple M1 Pro, macOS arm64, Go 1.26.3. 65,535 values and 100 unique
values, median of five runs.
| Case | Before | After | Change |
| --- | ---: | ---: | ---: |
| FLBA, width 16 | 4.59 ms | 3.09 ms | 33% faster |
| Int96 | 5.41 ms | 3.87 ms | 29% faster |
| FLBA allocations | 131,117 | 65,581 | 50% fewer |
| Int96 allocations | 196,655 | 131,118 | 33% fewer |
Allocated bytes also fell by about 42% for FLBA and 33% for Int96.
## Tests
- go test ./... with Arrow and Parquet test data
- go test ./parquet/internal/encoding/...
- go test -race ./parquet/internal/encoding/...
- go vet ./parquet/internal/encoding/...
- go test -tags noasm ./parquet/internal/encoding/...
---
.../internal/encoding/encoding_benchmarks_test.go | 69 ++++++++++++++++++++++
parquet/internal/encoding/encoding_test.go | 1 +
.../encoding/fixed_len_byte_array_encoder.go | 10 +++-
parquet/internal/encoding/typed_encoder.go | 7 ++-
4 files changed, 84 insertions(+), 3 deletions(-)
diff --git a/parquet/internal/encoding/encoding_benchmarks_test.go
b/parquet/internal/encoding/encoding_benchmarks_test.go
index 43bcc4ea..ab650dc1 100644
--- a/parquet/internal/encoding/encoding_benchmarks_test.go
+++ b/parquet/internal/encoding/encoding_benchmarks_test.go
@@ -424,6 +424,75 @@ func BenchmarkEncodeDictByteArray(b *testing.B) {
}
}
+func BenchmarkEncodeDictFixedLenByteArray(b *testing.B) {
+ const (
+ nunique = 100
+ nvalues = 65535
+ )
+
+ for _, width := range []int{8, 16, 32} {
+ b.Run(fmt.Sprintf("width=%d", width), func(b *testing.B) {
+ values := make([]parquet.FixedLenByteArray, nvalues)
+ for i := range values {
+ value := make([]byte, width)
+ unique := uint32(i % nunique)
+ for j := 0; j < 4; j++ {
+ value[j] = byte(unique >> uint(8*j))
+ }
+ for j := 4; j < width; j++ {
+ value[j] = byte(j)
+ }
+ values[i] = value
+ }
+
+ col := schema.NewColumn(schema.NewFixedLenByteArrayNode(
+ "fixedlenbytearray",
parquet.Repetitions.Required, int32(width), -1), 0, 0)
+ b.SetBytes(int64(nvalues * width))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ enc :=
encoding.NewEncoder(parquet.Types.FixedLenByteArray,
parquet.Encodings.PlainDict,
+ true, col,
memory.DefaultAllocator).(*encoding.DictFixedLenByteArrayEncoder)
+ enc.Put(values)
+ buf, err := enc.FlushValues()
+ if err != nil {
+ b.Fatal(err)
+ }
+ buf.Release()
+ enc.Release()
+ }
+ })
+ }
+}
+
+func BenchmarkEncodeDictInt96(b *testing.B) {
+ const (
+ nunique = 100
+ nvalues = 65535
+ )
+
+ values := make([]parquet.Int96, nvalues)
+ for i := range values {
+ values[i] = parquet.NewInt96([3]uint32{uint32(i % nunique), 0,
0})
+ }
+ col := schema.NewColumn(schema.NewInt96Node("int96",
parquet.Repetitions.Required, -1), 0, 0)
+
+ b.SetBytes(int64(nvalues * parquet.Int96SizeBytes))
+ b.ReportAllocs()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ enc := encoding.NewEncoder(parquet.Types.Int96,
parquet.Encodings.PlainDict,
+ true, col,
memory.DefaultAllocator).(*encoding.DictInt96Encoder)
+ enc.Put(values)
+ buf, err := enc.FlushValues()
+ if err != nil {
+ b.Fatal(err)
+ }
+ buf.Release()
+ enc.Release()
+ }
+}
+
func benchmarkEncodeDictNumeric[T int32 | int64 | float32 | float64](b
*testing.B, typ parquet.Type, col *schema.Column, valueSize int64) {
const (
nunique = 100
diff --git a/parquet/internal/encoding/encoding_test.go
b/parquet/internal/encoding/encoding_test.go
index 1589c133..27ce54a2 100644
--- a/parquet/internal/encoding/encoding_test.go
+++ b/parquet/internal/encoding/encoding_test.go
@@ -599,6 +599,7 @@ func TestDictEncoding(t *testing.T) {
{"Int64", reflect.TypeOf(int64(0))},
{"Float32", reflect.TypeOf(float32(0))},
{"Float64", reflect.TypeOf(float64(0))},
+ {"Int96", reflect.TypeOf(parquet.Int96{})},
{"ByteArray", reflect.TypeOf(parquet.ByteArray{})},
{"FixedLenByteArray",
reflect.TypeOf(parquet.FixedLenByteArray{})},
}
diff --git a/parquet/internal/encoding/fixed_len_byte_array_encoder.go
b/parquet/internal/encoding/fixed_len_byte_array_encoder.go
index cd3e0a84..26446e84 100644
--- a/parquet/internal/encoding/fixed_len_byte_array_encoder.go
+++ b/parquet/internal/encoding/fixed_len_byte_array_encoder.go
@@ -177,8 +177,13 @@ func (enc *DictFixedLenByteArrayEncoder) WriteDict(out
[]byte) {
// Put writes fixed length values to a dictionary encoded column
func (enc *DictFixedLenByteArrayEncoder) Put(in []parquet.FixedLenByteArray) {
+ if len(in) == 0 {
+ return
+ }
+
+ memo := enc.memo.(BinaryMemoTable)
for _, v := range in {
- memoIdx, found, err := enc.memo.GetOrInsert(v)
+ memoIdx, found, err := memo.InsertOrGet(v)
if err != nil {
panic(err)
}
@@ -241,8 +246,9 @@ func (enc *DictFixedLenByteArrayEncoder)
PutDictionary(values arrow.Array) error
enc.dictEncodedSize += enc.typeLen * values.Len()
data :=
values.Data().Buffers()[1].Bytes()[values.Data().Offset()*enc.typeLen:]
+ memo := enc.memo.(BinaryMemoTable)
for i := 0; i < values.Len(); i++ {
- _, _, err := enc.memo.GetOrInsert(data[i*enc.typeLen :
(i+1)*enc.typeLen])
+ _, _, err := memo.InsertOrGet(data[i*enc.typeLen :
(i+1)*enc.typeLen])
if err != nil {
return err
}
diff --git a/parquet/internal/encoding/typed_encoder.go
b/parquet/internal/encoding/typed_encoder.go
index 735ee925..b4861e08 100644
--- a/parquet/internal/encoding/typed_encoder.go
+++ b/parquet/internal/encoding/typed_encoder.go
@@ -462,8 +462,13 @@ func (enc *DictInt96Encoder) WriteDict(out []byte) {
// Put encodes the values passed in, adding to the index as needed
func (enc *DictInt96Encoder) Put(in []parquet.Int96) {
+ if len(in) == 0 {
+ return
+ }
+
+ memo := enc.memo.(BinaryMemoTable)
for _, v := range in {
- memoIdx, found, err := enc.memo.GetOrInsert(v[:])
+ memoIdx, found, err := memo.InsertOrGet(v[:])
if err != nil {
panic(err)
}