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 b02cc011 perf(arrow/compute): avoid getter wrappers in chunked binary 
take (#1359)
b02cc011 is described below

commit b02cc0111095ac8c1659300c2a276354ddd29294
Author: Minh Vu <[email protected]>
AuthorDate: Tue Oct 6 21:54:13 2026 +0200

    perf(arrow/compute): avoid getter wrappers in chunked binary take (#1359)
    
    ### What
    - Faster chunked string and binary `take`.
    
    ### Why
    Each lookup goes through an interface and a generic method wrapper.
    
    Apple M1 Pro, Go 1.26.3. 64 chunks x 4,096 rows, 8-byte values, 10%
    selected, sequential indices. Medians from 12 x 100ms samples per
    revision.
    
    | String values | Before | After | Time |
    | --- | ---: | ---: | ---: |
    | No nulls | 504.8 us | 484.8 us | -4.0% |
    | 1% nulls | 661.9 us | 632.4 us | -4.5% |
    | 50% nulls | 463.8 us | 440.7 us | -5.0% |
    | All nulls | 309.3 us | 287.1 us | -7.2% |
    
    Random access is mostly unchanged in 20 x 500ms samples. Allocations are
    unchanged.
    
    ### Implementation
    - Pass the concrete getter through dispatch.
    - Call value and validity helpers directly.
---
 arrow/compute/chunked_take_nulls_benchmark_test.go |  97 +++++++++++++++
 arrow/compute/chunked_take_nulls_test.go           | 132 +++++++++++++++++++++
 arrow/compute/internal/kernels/vector_selection.go |  12 +-
 3 files changed, 231 insertions(+), 10 deletions(-)

diff --git a/arrow/compute/chunked_take_nulls_benchmark_test.go 
b/arrow/compute/chunked_take_nulls_benchmark_test.go
new file mode 100644
index 00000000..bdc53d44
--- /dev/null
+++ b/arrow/compute/chunked_take_nulls_benchmark_test.go
@@ -0,0 +1,97 @@
+// 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.18
+
+package compute_test
+
+import (
+       "context"
+       "fmt"
+       "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 BenchmarkTakeChunkedBinaryNulls(b *testing.B) {
+       for _, typ := range chunkedTakeBinaryTypes {
+               for _, numChunks := range []int{8, 64} {
+                       for _, nullPattern := range []string{"none", "sparse", 
"alternating", "all"} {
+                               for _, order := range []string{"sequential", 
"random"} {
+                                       name := 
fmt.Sprintf("%s/chunks=%d/nulls=%s/%s", typ, numChunks, nullPattern, order)
+                                       b.Run(name, func(b *testing.B) {
+                                               mem := memory.DefaultAllocator
+                                               ctx := 
compute.WithAllocator(context.Background(), mem)
+                                               const rowsPerChunk = 4096
+                                               chunks := make([]arrow.Array, 
numChunks)
+                                               for i := range chunks {
+                                                       bldr := 
array.NewBinaryBuilder(mem, typ.(arrow.BinaryDataType))
+                                                       
bldr.Reserve(rowsPerChunk)
+                                                       
bldr.ReserveData(rowsPerChunk * 8)
+                                                       for j := 0; j < 
rowsPerChunk; j++ {
+                                                               isNull := 
nullPattern == "all" ||
+                                                                       
(nullPattern == "sparse" && j%100 == 0) ||
+                                                                       
(nullPattern == "alternating" && j%2 == 0)
+                                                               if isNull {
+                                                                       
bldr.AppendNull()
+                                                               } else {
+                                                                       
bldr.Append([]byte("value123"))
+                                                               }
+                                                       }
+                                                       chunks[i] = 
bldr.NewArray()
+                                                       bldr.Release()
+                                               }
+                                               values := arrow.NewChunked(typ, 
chunks)
+                                               defer values.Release()
+                                               for _, chunk := range chunks {
+                                                       chunk.Release()
+                                               }
+
+                                               totalRows := numChunks * 
rowsPerChunk
+                                               indicesBldr := 
array.NewInt64Builder(mem)
+                                               indicesBldr.Reserve(totalRows / 
10)
+                                               for i := 0; i < totalRows/10; 
i++ {
+                                                       idx := int64(i)
+                                                       if order == "random" {
+                                                               idx = 
(idx*1103515245 + 12345) % int64(totalRows)
+                                                       }
+                                                       indicesBldr.Append(idx)
+                                               }
+                                               indices := 
indicesBldr.NewArray()
+                                               indicesBldr.Release()
+                                               defer indices.Release()
+                                               valuesDatum := 
&compute.ChunkedDatum{Value: values}
+                                               indicesDatum := 
&compute.ArrayDatum{Value: indices.Data()}
+                                               opts := 
*compute.DefaultTakeOptions()
+
+                                               b.ReportAllocs()
+                                               b.ResetTimer()
+                                               for i := 0; i < b.N; i++ {
+                                                       result, err := 
compute.Take(ctx, opts, valuesDatum, indicesDatum)
+                                                       if err != nil {
+                                                               b.Fatal(err)
+                                                       }
+                                                       result.Release()
+                                               }
+                                       })
+                               }
+                       }
+               }
+       }
+}
diff --git a/arrow/compute/chunked_take_nulls_test.go 
b/arrow/compute/chunked_take_nulls_test.go
new file mode 100644
index 00000000..ae8d5a92
--- /dev/null
+++ b/arrow/compute/chunked_take_nulls_test.go
@@ -0,0 +1,132 @@
+// 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.18
+
+package compute_test
+
+import (
+       "context"
+       "fmt"
+       "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"
+       "github.com/stretchr/testify/require"
+)
+
+func TestChunkedBinaryTakeNullPatterns(t *testing.T) {
+       for _, typ := range chunkedTakeBinaryTypes {
+               for _, nullPattern := range []string{"none", "sparse", 
"alternating", "all", "mixed_chunks"} {
+                       for _, prefix := range []int{1, 3, 7, 8, 15} {
+                               t.Run(fmt.Sprintf("%s/%s/offset=%d", typ, 
nullPattern, prefix), func(t *testing.T) {
+                                       mem := 
memory.NewCheckedAllocator(memory.DefaultAllocator)
+                                       defer mem.AssertSize(t, 0)
+                                       ctx := 
compute.WithAllocator(context.Background(), mem)
+                                       const rowsPerChunk = 9
+                                       logicalValues := make([][]byte, 
2*rowsPerChunk)
+                                       logicalValid := make([]bool, 
len(logicalValues))
+                                       chunks := make([]arrow.Array, 0, 5)
+                                       empty := newTestBinaryArray(mem, typ, 
nil, nil)
+                                       defer empty.Release()
+                                       for chunkIndex := 0; chunkIndex < 2; 
chunkIndex++ {
+                                               values := make([][]byte, 
prefix+rowsPerChunk+1)
+                                               valid := make([]bool, 
len(values))
+                                               for i := range values {
+                                                       values[i] = 
[]byte("padding")
+                                                       valid[i] = true
+                                               }
+                                               for i := 0; i < rowsPerChunk; 
i++ {
+                                                       globalIndex := 
chunkIndex*rowsPerChunk + i
+                                                       value := 
[]byte(fmt.Sprintf("row-%d", globalIndex))
+                                                       if i == 0 || i == 
rowsPerChunk-1 {
+                                                               value = nil
+                                                       }
+                                                       isNull := nullPattern 
== "all" ||
+                                                               (nullPattern == 
"sparse" && i == 1) ||
+                                                               (nullPattern == 
"alternating" && i%2 == 0) ||
+                                                               (nullPattern == 
"mixed_chunks" && chunkIndex == 1 && i%2 == 0)
+                                                       values[prefix+i] = value
+                                                       valid[prefix+i] = 
!isNull
+                                                       
logicalValues[globalIndex] = value
+                                                       
logicalValid[globalIndex] = !isNull
+                                               }
+                                               full := newTestBinaryArray(mem, 
typ, values, valid)
+                                               if nullPattern == 
"mixed_chunks" && chunkIndex == 0 {
+                                                       buffers := 
append([]*memory.Buffer(nil), full.Data().Buffers()...)
+                                                       buffers[0] = nil
+                                                       data := 
array.NewData(typ, full.Len(), buffers, nil, 0, 0)
+                                                       full.Release()
+                                                       full = 
array.MakeFromData(data)
+                                                       data.Release()
+                                               }
+                                               sliced := array.NewSlice(full, 
int64(prefix), int64(prefix+rowsPerChunk))
+                                               full.Release()
+                                               chunks = append(chunks, empty, 
sliced)
+                                       }
+                                       chunks = append(chunks, empty)
+                                       values := arrow.NewChunked(typ, chunks)
+                                       defer values.Release()
+                                       for _, chunk := range chunks {
+                                               if chunk != empty {
+                                                       chunk.Release()
+                                               }
+                                       }
+
+                                       for _, indexType := range 
chunkedTakeIndexTypes {
+                                               t.Run(indexType.String(), 
func(t *testing.T) {
+                                                       selected := []int{17, 
0, 9, 8, 10, 9, 1, 16, 3, 17}
+                                                       indicesValues := 
make([]string, len(selected)+2)
+                                                       indicesValid := 
make([]bool, len(indicesValues))
+                                                       for i := range 
indicesValues {
+                                                               
indicesValues[i] = "99"
+                                                               indicesValid[i] 
= true
+                                                       }
+                                                       wantValues := 
make([][]byte, len(selected))
+                                                       wantValid := 
make([]bool, len(selected))
+                                                       for i, idx := range 
selected {
+                                                               if i == 4 {
+                                                                       
indicesValid[i+1] = false
+                                                                       continue
+                                                               }
+                                                               
indicesValues[i+1] = fmt.Sprint(idx)
+                                                               wantValues[i] = 
logicalValues[idx]
+                                                               wantValid[i] = 
logicalValid[idx]
+                                                       }
+                                                       indicesFull := 
makeChunkedTakeIndexArray(t, mem, indexType, indicesValues, indicesValid)
+                                                       defer 
indicesFull.Release()
+                                                       indices := 
array.NewSlice(indicesFull, 1, int64(1+len(selected)))
+                                                       defer indices.Release()
+                                                       result, err := 
compute.Take(ctx, *compute.DefaultTakeOptions(),
+                                                               
&compute.ChunkedDatum{Value: values}, &compute.ArrayDatum{Value: 
indices.Data()})
+                                                       require.NoError(t, err)
+                                                       defer result.Release()
+                                                       expectedArray := 
newTestBinaryArray(mem, typ, wantValues, wantValid)
+                                                       defer 
expectedArray.Release()
+                                                       expected := 
arrow.NewChunked(typ, []arrow.Array{expectedArray})
+                                                       defer expected.Release()
+                                                       actual := 
result.(*compute.ChunkedDatum).Value
+                                                       require.True(t, 
array.ChunkedEqual(expected, actual))
+                                                       require.Equal(t, 
expected.NullN(), actual.NullN())
+                                               })
+                                       }
+                               })
+                       }
+               }
+       }
+}
diff --git a/arrow/compute/internal/kernels/vector_selection.go 
b/arrow/compute/internal/kernels/vector_selection.go
index 174148f2..855551ca 100644
--- a/arrow/compute/internal/kernels/vector_selection.go
+++ b/arrow/compute/internal/kernels/vector_selection.go
@@ -700,14 +700,6 @@ func (c *chunkedPrimitiveGetter[T]) GetValue(i int64) T {
 func (c *chunkedPrimitiveGetter[T]) NullCount() int64 { return c.nulls }
 func (c *chunkedPrimitiveGetter[T]) Len() int64       { return c.len }
 
-type binaryGetter interface {
-       IsValid(int64) bool
-       GetValue(int64) []byte
-       NullCount() int64
-       Len() int64
-       DataLen() int64
-}
-
 type chunkedBinaryGetter[OffsetT int32 | int64] struct {
        resolver      *exec.ChunkResolver
        offsets       [][]OffsetT
@@ -1285,7 +1277,7 @@ func checkBinaryTakeOffset[OffsetT int32 | int64](offset 
OffsetT, valueLen int64
        return nil
 }
 
-func takeChunkedBinaryImpl[IdxT arrow.UintType, OffsetT int32 | int64](ctx 
*exec.KernelCtx, indices *exec.ArraySpan, values binaryGetter, out 
*exec.ExecResult) error {
+func takeChunkedBinaryImpl[IdxT arrow.UintType, OffsetT int32 | int64](ctx 
*exec.KernelCtx, indices *exec.ArraySpan, values *chunkedBinaryGetter[OffsetT], 
out *exec.ExecResult) error {
        var (
                indicesValues   = exec.GetSpanValues[IdxT](indices, 1)
                indicesIsValid  = bitutil.OptionalBitIndexer{Bitmap: 
indices.Buffers[0].Buf, Offset: int(indices.Offset)}
@@ -1422,7 +1414,7 @@ func takeChunkedBinaryImpl[IdxT arrow.UintType, OffsetT 
int32 | int64](ctx *exec
        return nil
 }
 
-func takeChunkedBinaryDispatch[IdxT arrow.UintType, OffsetT int32 | int64](ctx 
*exec.KernelCtx, values binaryGetter, indices *arrow.Chunked, out 
[]*exec.ExecResult) error {
+func takeChunkedBinaryDispatch[IdxT arrow.UintType, OffsetT int32 | int64](ctx 
*exec.KernelCtx, values *chunkedBinaryGetter[OffsetT], indices *arrow.Chunked, 
out []*exec.ExecResult) error {
        var span exec.ArraySpan
        for i, chunk := range indices.Chunks() {
                span.SetMembers(chunk.Data())

Reply via email to