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 58995de6 perf(arrow/array): bulk append BinaryView nulls (#1232)
58995de6 is described below

commit 58995de6452b818579e86ec8bed1881e24433a6f
Author: Minh Vu <[email protected]>
AuthorDate: Fri Aug 28 23:38:37 2026 +0200

    perf(arrow/array): bulk append BinaryView nulls (#1232)
    
    ## What
    
    - Replace the per-value validity updates in
    `BinaryViewBuilder.AppendNulls` with one bulk bitmap update.
    - Update the length and null count once per batch.
    - Treat zero and negative counts as no-ops.
    - Add parity coverage for aligned and unaligned starting positions.
    
    ## Benchmark
    
    Apple M1 Pro, Go 1.26.3. The builder is preallocated and reused, so this
    measures the bitmap append itself. Medians from 5 runs at offset 0:
    
    Command: `go test -vet=off ./arrow/array -run "^$" -bench
    "^BenchmarkBinaryViewBuilderAppendNulls$" -benchmem -benchtime=500ms
    -count=5 -cpu=1`
    
    | Values | Before | After |
    | ---: | ---: | ---: |
    | 1,024 | 764.8 ns/op | 19.66 ns/op |
    | 65,536 | 40,266 ns/op | 152.2 ns/op |
    
    Both versions use 0 B/op and 0 allocs/op in this preallocated benchmark.
    
    ## Tests
    
    - `go test ./arrow/array -count=1`
    - `PARQUET_TEST_DATA=parquet-testing/data go test ./... -count=1`
---
 arrow/array/binarybuilder.go                       |  13 ++-
 .../binaryview_builder_bulk_benchmark_test.go      |  47 +++++++++
 arrow/array/binaryview_builder_bulk_test.go        | 111 +++++++++++++++++++++
 3 files changed, 168 insertions(+), 3 deletions(-)

diff --git a/arrow/array/binarybuilder.go b/arrow/array/binarybuilder.go
index 8d7c5977..857e7fb5 100644
--- a/arrow/array/binarybuilder.go
+++ b/arrow/array/binarybuilder.go
@@ -606,10 +606,17 @@ func (b *BinaryViewBuilder) AppendNull() {
 }
 
 func (b *BinaryViewBuilder) AppendNulls(n int) {
-       b.Reserve(n)
-       for i := 0; i < n; i++ {
-               b.UnsafeAppendBoolToBitmap(false)
+       if n <= 0 {
+               return
        }
+       if n > math.MaxInt-b.length {
+               panic("arrow/array: builder length overflow")
+       }
+
+       b.Reserve(n)
+       bitutil.SetBitsTo(b.nullBitmap.Bytes(), int64(b.length), int64(n), 
false)
+       b.length += n
+       b.nulls += n
 }
 
 func (b *BinaryViewBuilder) AppendEmptyValue() {
diff --git a/arrow/array/binaryview_builder_bulk_benchmark_test.go 
b/arrow/array/binaryview_builder_bulk_benchmark_test.go
new file mode 100644
index 00000000..ff1043c4
--- /dev/null
+++ b/arrow/array/binaryview_builder_bulk_benchmark_test.go
@@ -0,0 +1,47 @@
+// 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
+
+import (
+       "fmt"
+       "testing"
+
+       "github.com/apache/arrow-go/v18/arrow/memory"
+)
+
+func BenchmarkBinaryViewBuilderAppendNulls(b *testing.B) {
+       for _, count := range []int{1024, 65536} {
+               for _, offset := range []int{0, 1, 7} {
+                       b.Run(fmt.Sprintf("count_%d/offset_%d", count, offset), 
func(b *testing.B) {
+                               builder := 
NewBinaryViewBuilder(memory.DefaultAllocator)
+                               builder.Resize(count + offset)
+                               builder.length = offset
+                               defer builder.Release()
+
+                               b.ReportAllocs()
+                               b.SetBytes(int64(count))
+                               b.ResetTimer()
+                               for b.Loop() {
+                                       builder.AppendNulls(count)
+                                       builder.length = offset
+                                       builder.nulls = 0
+                               }
+                       })
+               }
+       }
+}
diff --git a/arrow/array/binaryview_builder_bulk_test.go 
b/arrow/array/binaryview_builder_bulk_test.go
new file mode 100644
index 00000000..f7abd5d0
--- /dev/null
+++ b/arrow/array/binaryview_builder_bulk_test.go
@@ -0,0 +1,111 @@
+// 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"
+       "math"
+       "testing"
+
+       "github.com/apache/arrow-go/v18/arrow/array"
+       "github.com/apache/arrow-go/v18/arrow/memory"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+)
+
+func TestBinaryViewBuilderBulkAppendNulls(t *testing.T) {
+       starts := []int{0, 1, 7, 8, 9, 15, 16, 17}
+       batchSizes := []int{-1, 0, 1, 2, 7, 8, 9, 16, 17}
+
+       for _, start := range starts {
+               for _, batchSize := range batchSizes {
+                       t.Run(fmt.Sprintf("start_%d_batch_%d", start, 
batchSize), func(t *testing.T) {
+                               mem := 
memory.NewCheckedAllocator(memory.NewGoAllocator())
+                               defer mem.AssertSize(t, 0)
+
+                               bulk := array.NewBinaryViewBuilder(mem)
+                               defer bulk.Release()
+                               scalar := array.NewBinaryViewBuilder(mem)
+                               defer scalar.Release()
+
+                               appendBinaryViewBuilderPrefix(bulk, start)
+                               appendBinaryViewBuilderPrefix(scalar, start)
+                               bulk.AppendNulls(batchSize)
+                               for i := 0; i < batchSize; i++ {
+                                       scalar.AppendNull()
+                               }
+
+                               bulk.Append([]byte("tail"))
+                               scalar.Append([]byte("tail"))
+
+                               assertBinaryViewBuilderArrayParity(t, bulk, 
scalar)
+                       })
+               }
+       }
+}
+
+func TestBinaryViewBuilderAppendNullsRejectsLengthOverflow(t *testing.T) {
+       mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+       defer mem.AssertSize(t, 0)
+
+       builder := array.NewBinaryViewBuilder(mem)
+       defer builder.Release()
+       builder.AppendEmptyValue()
+
+       assert.PanicsWithValue(t, "arrow/array: builder length overflow", 
func() {
+               builder.AppendNulls(math.MaxInt)
+       })
+       assert.Equal(t, 1, builder.Len())
+       assert.Equal(t, 0, builder.NullN())
+
+       arr := builder.NewArray().(*array.BinaryView)
+       defer arr.Release()
+       require.NoError(t, arr.ValidateFull())
+       assert.Equal(t, 1, arr.Len())
+       assert.Equal(t, 0, arr.NullN())
+       assert.True(t, arr.IsValid(0))
+}
+
+func appendBinaryViewBuilderPrefix(builder *array.BinaryViewBuilder, n int) {
+       for i := 0; i < n; i++ {
+               switch i % 3 {
+               case 0:
+                       builder.Append([]byte(fmt.Sprintf("value-%d", i)))
+               case 1:
+                       builder.AppendNull()
+               case 2:
+                       builder.AppendEmptyValue()
+               }
+       }
+}
+
+func assertBinaryViewBuilderArrayParity(t *testing.T, bulk, scalar 
*array.BinaryViewBuilder) {
+       t.Helper()
+
+       assert.Equal(t, scalar.Len(), bulk.Len())
+       assert.Equal(t, scalar.NullN(), bulk.NullN())
+
+       bulkArray := bulk.NewArray()
+       defer bulkArray.Release()
+       scalarArray := scalar.NewArray()
+       defer scalarArray.Release()
+
+       require.NoError(t, bulkArray.(interface{ ValidateFull() error 
}).ValidateFull())
+       require.NoError(t, scalarArray.(interface{ ValidateFull() error 
}).ValidateFull())
+       assert.True(t, array.Equal(bulkArray, scalarArray))
+}

Reply via email to