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 <[email protected]>
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)