You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by ze...@apache.org on 2026/08/28 21:38:27 UTC

(arrow-go) branch main updated: perf(compute): reserve binary view cast payload (#1227)

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 16c738f7 perf(compute): reserve binary view cast payload (#1227)
16c738f7 is described below

commit 16c738f7101e057f2ab053d8d22caad693d3c364
Author: Minh Vu <vu...@gmail.com>
AuthorDate: Fri Aug 28 23:38:21 2026 +0200

    perf(compute): reserve binary view cast payload (#1227)
    
    ## Summary
    
    - Reserve the output payload buffer after the existing `BinaryView` scan
    computes the total payload size.
    - Keep the existing overflow check and append path unchanged.
    - Add a benchmark for 64K values with 4, 32, and 256 byte payloads, plus
    0%, 10%, and 50% nulls.
    
    This avoids repeated growth copies when materializing out-of-line
    `BinaryView` values as `Binary`.
    
    ## Benchmark
    
    Measured on Apple M1 Pro, macOS arm64, Go 1.26.3. Median of 3 runs. Each
    result is `time/op`, `B/op`, and `allocs/op`.
    
    | Payload | Nulls | Before | After |
    | --- | ---: | --- | --- |
    | 4 B | 0% | 1.46 ms, 1.12 MB, 66 allocs | 1.41 ms, 0.82 MB, 54 allocs |
    | 32 B | 0% | 1.96 ms, 4.81 MB, 70 allocs | 1.69 ms, 2.66 MB, 54 allocs
    |
    | 256 B | 0% | 4.20 ms, 34.2 MB, 71 allocs | 2.93 ms, 17.3 MB, 55 allocs
    |
    | 256 B | 50% | 2.55 ms, 17.4 MB, 70 allocs | 1.87 ms, 8.95 MB, 55
    allocs |
    
    The benchmark matrix also covers 10% nulls. The samples reduce payload
    allocation by 26% to 54% and remove 12 to 16 allocations per operation.
    
    Command:
    
    ```text
    go test -vet=off ./arrow/compute -run '^$' -bench '^BenchmarkBinaryViewToBinaryMaterialization$' -benchmem -benchtime=500ms -count=3 -cpu=1
    ```
    
    ## Tests
    
    - `go test ./arrow/compute ./arrow/compute/internal/kernels -count=1`
    - `go test ./... -count=1` with the pinned `arrow-testing` and
    `parquet-testing` submodules initialized
---
 arrow/compute/cast_binary_view_benchmark_test.go   | 72 ++++++++++++++++++++++
 .../compute/internal/kernels/binary_view_casts.go  |  8 +++
 2 files changed, 80 insertions(+)

diff --git a/arrow/compute/cast_binary_view_benchmark_test.go b/arrow/compute/cast_binary_view_benchmark_test.go
new file mode 100644
index 00000000..a9e1b11a
--- /dev/null
+++ b/arrow/compute/cast_binary_view_benchmark_test.go
@@ -0,0 +1,72 @@
+// 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.
+
+//go:build go1.24
+
+package compute_test
+
+import (
+	"context"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/apache/arrow-go/v18/arrow"
+	"github.com/apache/arrow-go/v18/arrow/array"
+	"github.com/apache/arrow-go/v18/arrow/compute"
+	"github.com/apache/arrow-go/v18/arrow/memory"
+)
+
+func BenchmarkBinaryViewToBinaryMaterialization(b *testing.B) {
+	for _, valueLen := range []int{4, 32, 256} {
+		for _, nullEvery := range []int{0, 10, 2} {
+			name := fmt.Sprintf("value-len=%d/null-every=%d", valueLen, nullEvery)
+			b.Run(name, func(b *testing.B) {
+				const count = 64 * 1024
+
+				values := make([][]byte, count)
+				valid := make([]bool, count)
+				value := []byte(strings.Repeat("x", valueLen))
+				validCount := 0
+				for i := range values {
+					values[i] = value
+					valid[i] = nullEvery == 0 || i%nullEvery != 0
+					if valid[i] {
+						validCount++
+					}
+				}
+
+				builder := array.NewBinaryViewBuilder(memory.DefaultAllocator)
+				builder.AppendValues(values, valid)
+				input := builder.NewArray()
+				builder.Release()
+				defer input.Release()
+
+				opts := compute.SafeCastOptions(arrow.BinaryTypes.Binary)
+				b.SetBytes(int64(validCount * valueLen))
+				b.ReportAllocs()
+				b.ResetTimer()
+				for b.Loop() {
+					out, err := compute.CastArray(context.Background(), input, opts)
+					if err != nil {
+						b.Fatal(err)
+					}
+					out.Release()
+				}
+			})
+		}
+	}
+}
diff --git a/arrow/compute/internal/kernels/binary_view_casts.go b/arrow/compute/internal/kernels/binary_view_casts.go
index 01a5c384..7092e0a2 100644
--- a/arrow/compute/internal/kernels/binary_view_casts.go
+++ b/arrow/compute/internal/kernels/binary_view_casts.go
@@ -250,6 +250,14 @@ func CastBinaryViewToBinary[OutOffsetT int32 | int64](ctx *exec.KernelCtx, batch
 		return fmt.Errorf("%w: failed casting from %s to %s: input array too large",
 			arrow.ErrInvalid, input.Type, out.Type)
 	}
+	maxInt := int64(^uint(0) >> 1)
+	if totalBytes > maxInt {
+		return fmt.Errorf("%w: failed casting from %s to %s: output data buffer too large for this platform",
+			arrow.ErrInvalid, input.Type, out.Type)
+	}
+	if totalBytes > 0 {
+		ba.reserveData(int(totalBytes))
+	}
 
 	appendBinaryValues(arr, getVal, ba)