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 846fda47 perf(parquet/variant): skip metadata construction in Encode 
(#1217)
846fda47 is described below

commit 846fda471c4fafc991492af3d374775aacedcbbc
Author: Minh Vu <[email protected]>
AuthorDate: Fri Aug 28 22:32:12 2026 +0200

    perf(parquet/variant): skip metadata construction in Encode (#1217)
    
    ## Summary
    
    - **Skip metadata construction in `Encode`** for primitive values.
    - Keep `Of` unchanged for callers that need a complete `Value`.
    - Add byte-for-byte parity coverage for primitive types, boundaries, and
    options.
    - Add benchmarks for the encoded and metadata-building paths.
    
    ## Benchmark
    
    Apple M1 Pro, darwin/arm64. Times are rounded medians from 5 runs. `Of`
    is the previous metadata-building path.
    
    | Input | `Encode` | `Of` |
    | --- | ---: | ---: |
    | bool | 84 ns, 176 B, 3 allocs | 103 ns, 179 B, 4 allocs |
    | int64 max | 101 ns, 192 B, 5 allocs | 126 ns, 200 B, 6 allocs |
    | short string | 96 ns, 192 B, 4 allocs | 121 ns, 195 B, 5 allocs |
    | long string | 154 ns, 276 B, 6 allocs | 182 ns, 280 B, 7 allocs |
    | decimal128 | 148 ns, 216 B, 6 allocs | 167 ns, 224 B, 7 allocs |
    
    ## Tests
    
    - `go test ./parquet/variant -count=1`
    - `go test -race ./parquet/variant -run '^TestEncodeMatchesOf$'
    -count=1`
    - `go vet ./parquet/variant`
    - `PARQUET_TEST_DATA="$PWD/parquet-testing/data" go test -count=1 ./...`
---
 parquet/variant/builder.go     |  11 +-
 parquet/variant/encode_test.go | 287 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 292 insertions(+), 6 deletions(-)

diff --git a/parquet/variant/builder.go b/parquet/variant/builder.go
index 68fc178d..cf6a6ce8 100644
--- a/parquet/variant/builder.go
+++ b/parquet/variant/builder.go
@@ -892,14 +892,13 @@ type variantPrimitiveType interface {
 }
 
 // Encode is a convenience function that produces the encoded bytes for a 
primitive
-// variant value. At the moment this is just delegating to the 
[Builder.Append] method,
-// but in the future it will be optimized to avoid the extra overhead and 
reduce allocations.
+// variant value. It does not construct metadata since primitive values do not 
use it.
 func Encode[T variantPrimitiveType](v T, opt ...AppendOpt) ([]byte, error) {
-       out, err := Of(v, opt...)
-       if err != nil {
-               return nil, fmt.Errorf("failed to encode variant value: %w", 
err)
+       var b Builder
+       if err := b.Append(v, opt...); err != nil {
+               return nil, fmt.Errorf("failed to encode variant value: failed 
to append value: %w", err)
        }
-       return out.value, nil
+       return b.BuildWithoutMeta(), nil
 }
 
 func Of[T variantPrimitiveType](v T, opt ...AppendOpt) (Value, error) {
diff --git a/parquet/variant/encode_test.go b/parquet/variant/encode_test.go
new file mode 100644
index 00000000..407cac44
--- /dev/null
+++ b/parquet/variant/encode_test.go
@@ -0,0 +1,287 @@
+// 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 variant_test
+
+import (
+       "math"
+       "strings"
+       "testing"
+       "time"
+
+       "github.com/apache/arrow-go/v18/arrow"
+       "github.com/apache/arrow-go/v18/arrow/decimal"
+       "github.com/apache/arrow-go/v18/arrow/decimal128"
+       "github.com/apache/arrow-go/v18/parquet/variant"
+       "github.com/google/uuid"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+type encodeTestCase struct {
+       name   string
+       encode func() ([]byte, error)
+       of     func() (variant.Value, error)
+}
+
+func encodeTestCases() []encodeTestCase {
+       testTime := time.Date(2023, 5, 15, 14, 30, 0, 123456789, 
time.FixedZone("test", 2*60*60))
+       shortString := strings.Repeat("a", 63)
+       longString := shortString + "b"
+       binaryValue := []byte{0, 1, 2, 0xff}
+       emptyBinary := []byte{}
+       uuidValue := uuid.MustParse("00112233-4455-6677-8899-aabbccddeeff")
+       decimal4Value := variant.DecimalValue[decimal.Decimal32]{Scale: 2, 
Value: decimal.Decimal32(1234)}
+       decimal8Value := variant.DecimalValue[decimal.Decimal64]{Scale: 2, 
Value: decimal.Decimal64(1234567890)}
+       decimal16Value := variant.DecimalValue[decimal.Decimal128]{
+               Scale: 2, Value: decimal128.FromU64(1234567891234567890),
+       }
+
+       return []encodeTestCase{
+               {
+                       name:   "bool_true",
+                       encode: func() ([]byte, error) { return 
variant.Encode(true) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(true) },
+               },
+               {
+                       name:   "bool_false",
+                       encode: func() ([]byte, error) { return 
variant.Encode(false) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(false) },
+               },
+               {
+                       name:   "int8_min",
+                       encode: func() ([]byte, error) { return 
variant.Encode(int8(-128)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(int8(-128)) },
+               },
+               {
+                       name:   "uint8_max",
+                       encode: func() ([]byte, error) { return 
variant.Encode(uint8(255)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(uint8(255)) },
+               },
+               {
+                       name:   "int16_min",
+                       encode: func() ([]byte, error) { return 
variant.Encode(int16(-32768)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(int16(-32768)) },
+               },
+               {
+                       name:   "uint16_max",
+                       encode: func() ([]byte, error) { return 
variant.Encode(uint16(65535)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(uint16(65535)) },
+               },
+               {
+                       name:   "int32_min",
+                       encode: func() ([]byte, error) { return 
variant.Encode(int32(-2147483648)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(int32(-2147483648)) },
+               },
+               {
+                       name:   "uint32_max",
+                       encode: func() ([]byte, error) { return 
variant.Encode(uint32(1<<32 - 1)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(uint32(1<<32 - 1)) },
+               },
+               {
+                       name:   "int64_min",
+                       encode: func() ([]byte, error) { return 
variant.Encode(int64(-1 << 63)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(int64(-1 << 63)) },
+               },
+               {
+                       name:   "int64_max",
+                       encode: func() ([]byte, error) { return 
variant.Encode(int64(1<<63 - 1)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(int64(1<<63 - 1)) },
+               },
+               {
+                       name:   "int",
+                       encode: func() ([]byte, error) { return 
variant.Encode(int(123456)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(int(123456)) },
+               },
+               {
+                       name:   "uint",
+                       encode: func() ([]byte, error) { return 
variant.Encode(uint(123456)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(uint(123456)) },
+               },
+               {
+                       name:   "float32",
+                       encode: func() ([]byte, error) { return 
variant.Encode(float32(math.MaxFloat32)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(float32(math.MaxFloat32)) },
+               },
+               {
+                       name:   "float64",
+                       encode: func() ([]byte, error) { return 
variant.Encode(math.Copysign(0, -1)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(math.Copysign(0, -1)) },
+               },
+               {
+                       name:   "date",
+                       encode: func() ([]byte, error) { return 
variant.Encode(arrow.Date32(-2147483648)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(arrow.Date32(-2147483648)) },
+               },
+               {
+                       name:   "time",
+                       encode: func() ([]byte, error) { return 
variant.Encode(arrow.Time64(-123456789)) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(arrow.Time64(-123456789)) },
+               },
+               {
+                       name: "timestamp",
+                       encode: func() ([]byte, error) {
+                               return 
variant.Encode(arrow.Timestamp(-123456789), variant.OptTimestampNano, 
variant.OptTimestampUTC)
+                       },
+                       of: func() (variant.Value, error) {
+                               return variant.Of(arrow.Timestamp(-123456789), 
variant.OptTimestampNano, variant.OptTimestampUTC)
+                       },
+               },
+               {
+                       name:   "short_string",
+                       encode: func() ([]byte, error) { return 
variant.Encode(shortString) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(shortString) },
+               },
+               {
+                       name:   "empty_string",
+                       encode: func() ([]byte, error) { return 
variant.Encode("") },
+                       of:     func() (variant.Value, error) { return 
variant.Of("") },
+               },
+               {
+                       name:   "long_string",
+                       encode: func() ([]byte, error) { return 
variant.Encode(longString) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(longString) },
+               },
+               {
+                       name:   "binary",
+                       encode: func() ([]byte, error) { return 
variant.Encode(binaryValue) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(binaryValue) },
+               },
+               {
+                       name:   "empty_binary",
+                       encode: func() ([]byte, error) { return 
variant.Encode(emptyBinary) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(emptyBinary) },
+               },
+               {
+                       name: "time_default",
+                       encode: func() ([]byte, error) {
+                               return variant.Encode(testTime)
+                       },
+                       of: func() (variant.Value, error) {
+                               return variant.Of(testTime)
+                       },
+               },
+               {
+                       name: "time_nanos_utc",
+                       encode: func() ([]byte, error) {
+                               return variant.Encode(testTime, 
variant.OptTimestampNano, variant.OptTimestampUTC)
+                       },
+                       of: func() (variant.Value, error) {
+                               return variant.Of(testTime, 
variant.OptTimestampNano, variant.OptTimestampUTC)
+                       },
+               },
+               {
+                       name: "time_as_date",
+                       encode: func() ([]byte, error) {
+                               return variant.Encode(testTime, 
variant.OptTimeAsDate)
+                       },
+                       of: func() (variant.Value, error) {
+                               return variant.Of(testTime, 
variant.OptTimeAsDate)
+                       },
+               },
+               {
+                       name: "time_as_time",
+                       encode: func() ([]byte, error) {
+                               return variant.Encode(testTime, 
variant.OptTimeAsTime)
+                       },
+                       of: func() (variant.Value, error) {
+                               return variant.Of(testTime, 
variant.OptTimeAsTime)
+                       },
+               },
+               {
+                       name:   "uuid",
+                       encode: func() ([]byte, error) { return 
variant.Encode(uuidValue) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(uuidValue) },
+               },
+               {
+                       name:   "decimal4",
+                       encode: func() ([]byte, error) { return 
variant.Encode(decimal4Value) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(decimal4Value) },
+               },
+               {
+                       name:   "decimal8",
+                       encode: func() ([]byte, error) { return 
variant.Encode(decimal8Value) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(decimal8Value) },
+               },
+               {
+                       name:   "decimal16",
+                       encode: func() ([]byte, error) { return 
variant.Encode(decimal16Value) },
+                       of:     func() (variant.Value, error) { return 
variant.Of(decimal16Value) },
+               },
+       }
+}
+
+func TestEncodeMatchesOf(t *testing.T) {
+       for _, tc := range encodeTestCases() {
+               t.Run(tc.name, func(t *testing.T) {
+                       encoded, err := tc.encode()
+                       require.NoError(t, err)
+
+                       value, err := tc.of()
+                       require.NoError(t, err)
+
+                       assert.Equal(t, value.Bytes(), encoded)
+               })
+       }
+}
+
+var encodeBenchmarkSink []byte
+
+func benchmarkEncode(b *testing.B, encode func() ([]byte, error)) {
+       b.Helper()
+       b.ReportAllocs()
+       b.ResetTimer()
+
+       for range b.N {
+               encoded, err := encode()
+               if err != nil {
+                       b.Fatal(err)
+               }
+               encodeBenchmarkSink = encoded
+       }
+}
+
+func benchmarkOf(b *testing.B, of func() (variant.Value, error)) {
+       b.Helper()
+       b.ReportAllocs()
+       b.ResetTimer()
+
+       for range b.N {
+               value, err := of()
+               if err != nil {
+                       b.Fatal(err)
+               }
+               encodeBenchmarkSink = value.Bytes()
+       }
+}
+
+func BenchmarkEncode(b *testing.B) {
+       for _, tc := range encodeTestCases() {
+               tc := tc
+               b.Run(tc.name, func(b *testing.B) {
+                       benchmarkEncode(b, tc.encode)
+               })
+       }
+}
+
+func BenchmarkOf(b *testing.B) {
+       for _, tc := range encodeTestCases() {
+               tc := tc
+               b.Run(tc.name, func(b *testing.B) {
+                       benchmarkOf(b, tc.of)
+               })
+       }
+}

Reply via email to