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 66c27c3d perf(arrow/array): reuse full chunks in NewChunkedSlice 
(#1257)
66c27c3d is described below

commit 66c27c3d5740e7e833ef75b3f6522eabddb18fba
Author: Minh Vu <[email protected]>
AuthorDate: Mon Aug 31 22:38:43 2026 +0200

    perf(arrow/array): reuse full chunks in NewChunkedSlice (#1257)
    
    ## What
    
    - Reuse the original chunk when `NewChunkedSlice` selects the full
    chunk.
    - Keep using `NewSlice` for partial chunks.
    - Preserve the existing ownership pattern by retaining the reused chunks
    before passing them to `NewChunked`.
    
    This avoids creating an `ArrayData` and typed array wrapper for every
    full chunk in an aligned slice.
    
    ## Benchmark
    
    Apple M1 Pro, Go 1.26.3, 7 samples, one CPU:
    
    `go test -vet=off ./arrow/array -run '^$' -bench
    '^BenchmarkNewChunkedSlice$' -benchmem -benchtime=150ms -count=7 -cpu=1`
    
    Aligned, non-null input with 64 rows per chunk:
    
    | Chunks | Before | After |
    | ---: | --- | --- |
    | 64 | 10.122 us, 13.312 KiB, 131 allocs | 2.264 us, 2.312 KiB, 3 allocs
    |
    | 1,024 | 173.15 us, 212.06 KiB, 2,051 allocs | 37.14 us, 36.06 KiB, 3
    allocs |
    | 8,192 | 1.383 ms, 1.664 MiB, 16,387 allocs | 272.3 us, 256.1 KiB, 3
    allocs |
    
    Across all 12 benchmark cases, the geometric mean changed from **136.8
    us to 29.24 us** (-78.63%), **167.5 KiB to 28.44 KiB** (-83.02%), and
    **1,639 to 4.583 allocations** (-99.72%).
    
    ## Tests
    
    - `PARQUET_TEST_DATA=parquet-testing/data
    ARROW_TEST_DATA=arrow-testing/data go test ./... -count=1`
---
 arrow/array/chunked_slice_benchmark_test.go | 87 +++++++++++++++++++++++++++++
 arrow/array/chunked_slice_test.go           | 81 +++++++++++++++++++++++++++
 arrow/array/table.go                        |  7 ++-
 3 files changed, 174 insertions(+), 1 deletion(-)

diff --git a/arrow/array/chunked_slice_benchmark_test.go 
b/arrow/array/chunked_slice_benchmark_test.go
new file mode 100644
index 00000000..1713b2a3
--- /dev/null
+++ b/arrow/array/chunked_slice_benchmark_test.go
@@ -0,0 +1,87 @@
+// 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 array_test
+
+import (
+       "fmt"
+       "testing"
+
+       "github.com/apache/arrow-go/v18/arrow"
+       "github.com/apache/arrow-go/v18/arrow/array"
+       "github.com/apache/arrow-go/v18/arrow/memory"
+)
+
+var benchmarkNewChunkedSliceSink int
+
+func BenchmarkNewChunkedSlice(b *testing.B) {
+       const rowsPerChunk = 64
+
+       for _, numChunks := range []int{64, 1024, 8192} {
+               for _, withNulls := range []bool{false, true} {
+                       for _, partial := range []bool{false, true} {
+                               name := 
fmt.Sprintf("chunks=%d/nulls=%t/partial=%t", numChunks, withNulls, partial)
+                               b.Run(name, func(b *testing.B) {
+                                       input := 
makeChunkedSliceBenchmarkInput(numChunks, rowsPerChunk, withNulls)
+                                       defer input.Release()
+
+                                       var start, end int64
+                                       if partial {
+                                               start = 1
+                                               end = int64(input.Len() - 1)
+                                       } else {
+                                               end = int64(input.Len())
+                                       }
+
+                                       b.ReportAllocs()
+                                       b.ResetTimer()
+                                       for i := 0; i < b.N; i++ {
+                                               result := 
array.NewChunkedSlice(input, start, end)
+                                               benchmarkNewChunkedSliceSink = 
result.Len()
+                                               result.Release()
+                                       }
+                               })
+                       }
+               }
+       }
+}
+
+func makeChunkedSliceBenchmarkInput(numChunks, rowsPerChunk int, withNulls 
bool) *arrow.Chunked {
+       chunks := make([]arrow.Array, numChunks)
+       values := make([]int64, rowsPerChunk)
+       validity := make([]bool, rowsPerChunk)
+       for i := range values {
+               values[i] = int64(i)
+               validity[i] = i%10 != 0
+       }
+
+       for i := range chunks {
+               builder := array.NewInt64Builder(memory.DefaultAllocator)
+               if withNulls {
+                       builder.AppendValues(values, validity)
+               } else {
+                       builder.AppendValues(values, nil)
+               }
+               chunks[i] = builder.NewArray()
+               builder.Release()
+       }
+
+       result := arrow.NewChunked(arrow.PrimitiveTypes.Int64, chunks)
+       for _, chunk := range chunks {
+               chunk.Release()
+       }
+       return result
+}
diff --git a/arrow/array/chunked_slice_test.go 
b/arrow/array/chunked_slice_test.go
new file mode 100644
index 00000000..4251ed69
--- /dev/null
+++ b/arrow/array/chunked_slice_test.go
@@ -0,0 +1,81 @@
+// 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 array_test
+
+import (
+       "testing"
+
+       "github.com/apache/arrow-go/v18/arrow"
+       "github.com/apache/arrow-go/v18/arrow/array"
+       "github.com/apache/arrow-go/v18/arrow/memory"
+       "github.com/stretchr/testify/require"
+)
+
+func TestNewChunkedSliceRetainsFullChunks(t *testing.T) {
+       for _, tc := range []struct {
+               name       string
+               begin, end int64
+               want       []int32
+               valid      []bool
+               fullChunks []int
+       }{
+               {"all", 0, 6, []int32{1, 2, 3, 4, 5, 6}, []bool{true, false, 
true, true, false, true}, []int{0, 1, 2}},
+               {"middle", 2, 4, []int32{3, 4}, []bool{true, true}, []int{1}},
+               {"partial ends", 1, 5, []int32{2, 3, 4, 5}, []bool{false, true, 
true, false}, []int{-1, 1, -1}},
+               {"empty", 6, 6, nil, nil, nil},
+       } {
+               t.Run(tc.name, func(t *testing.T) {
+                       mem := 
memory.NewCheckedAllocator(memory.DefaultAllocator)
+                       defer mem.AssertSize(t, 0)
+
+                       builder := array.NewInt32Builder(mem)
+                       builder.AppendValues([]int32{0, 1, 2, 3, 4, 5, 6, 7}, 
[]bool{true, true, false, true, true, false, true, true})
+                       backing := builder.NewInt32Array()
+                       builder.Release()
+                       chunks := []arrow.Array{
+                               array.NewSlice(backing, 1, 3),
+                               array.NewSlice(backing, 3, 5),
+                               array.NewSlice(backing, 5, 7),
+                       }
+                       backing.Release()
+                       input := arrow.NewChunked(arrow.PrimitiveTypes.Int32, 
chunks)
+                       for _, chunk := range chunks {
+                               chunk.Release()
+                       }
+                       result := array.NewChunkedSlice(input, tc.begin, tc.end)
+                       defer result.Release()
+                       input.Release()
+
+                       require.Equal(t, len(tc.want), result.Len())
+                       require.Len(t, result.Chunks(), len(tc.fullChunks))
+                       position := 0
+                       for i, chunk := range result.Chunks() {
+                               if full := tc.fullChunks[i]; full >= 0 {
+                                       require.Same(t, chunks[full], chunk)
+                               }
+                               values := chunk.(*array.Int32)
+                               for j := range values.Len() {
+                                       require.Equal(t, tc.valid[position], 
values.IsValid(j))
+                                       if tc.valid[position] {
+                                               require.Equal(t, 
tc.want[position], values.Value(j))
+                                       }
+                                       position++
+                               }
+                       }
+               })
+       }
+}
diff --git a/arrow/array/table.go b/arrow/array/table.go
index 9e3e83c7..04b83f5d 100644
--- a/arrow/array/table.go
+++ b/arrow/array/table.go
@@ -69,7 +69,12 @@ func NewChunkedSlice(a *arrow.Chunked, i, j int64) 
*arrow.Chunked {
                if end > int64(arr.Len()) {
                        end = int64(arr.Len())
                }
-               chunks = append(chunks, NewSlice(arr, beg, end))
+               if beg == 0 && end == int64(arr.Len()) {
+                       arr.Retain()
+                       chunks = append(chunks, arr)
+               } else {
+                       chunks = append(chunks, NewSlice(arr, beg, end))
+               }
                sz -= int64(arr.Len()) - beg
                beg = 0
                cur++

Reply via email to