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 4bcea877 perf(arrow/array): append fixed-width nulls in bulk (#1173)
4bcea877 is described below
commit 4bcea877462c42d01281317e54b296d5125f7ad8
Author: Minh Vu <[email protected]>
AuthorDate: Mon Aug 31 19:39:05 2026 +0200
perf(arrow/array): append fixed-width nulls in bulk (#1173)
### Rationale for this change
Fixed-width builders currently implement `AppendNulls` by calling
`AppendNull` for every value. Large batches repeatedly reserve capacity,
update the validity bitmap, and update builder counters.
### What changes are included in this PR?
- Reserve capacity once for a null batch.
- Clear the validity range in bulk.
- Update the length and null count once.
- Advance fixed-size binary value storage once.
- Keep the existing scalar path for a single null.
- Keep the existing builder capacity growth policy.
- Size fixed-size binary value storage to the effective builder capacity
during `Resize`.
- Update the numeric builder template and generated source.
- Add correctness tests and benchmarks for representative fixed-width
builders.
Null values and array results are unchanged.
`FixedSizeBinaryBuilder.Resize` now reserves value storage together with
validity storage. Empty-value appends are separate because their value
bytes must also be zeroed.
### Benchmark
`BenchmarkFixedWidthBuilderAppendNulls` covers:
- `Int32`
- `Int64`
- `Decimal128`
- `Timestamp`
- `Boolean`
- `FixedSizeBinary(16)`
- Batch sizes of 1, 8, 64, 1,024, and 65,536
The benchmark creates a fresh builder for each iteration, so allocation
costs are included. The same benchmark was run against `upstream/main`
and this branch.
Allocation results for 65,536 nulls, checked against current
`upstream/main` and this branch. Three runs per case on Apple M1 Pro
with Go 1.26.3:
| Builder | Before B/op | After B/op | Before allocs/op | After
allocs/op |
| --- | ---: | ---: | ---: | ---: |
| Int32 | 580,864 | 551,168 | 23 | 5 |
| Int64 | 1,113,152 | 1,075,456 | 23 | 5 |
| Decimal128 | 2,169,655 | 2,124,080 | 25 | 7 |
| Timestamp | 1,113,232 | 1,075,536 | 24 | 6 |
| Boolean | 38,272 | 37,120 | 19 | 5 |
| FixedSizeBinary(16) | 2,170,319 | 2,124,104 | 28 | 7 |
Command:
```bash
go test -trimpath -p 1 ./arrow/array -run '^$' -bench
'^BenchmarkFixedWidthBuilderAppendNulls$/./count-65536$' -benchmem
-benchtime=150ms -count=3 -cpu=1
```
### Are these changes tested?
Yes.
- `go test ./arrow/array -count=1`
- `go test -race ./arrow/array -count=1`
- `go vet -composites=false ./arrow/array`
- Regenerated the numeric builders and verified there is no diff.
### Are there any user-facing changes?
`FixedSizeBinaryBuilder.Resize` now reserves the value buffer eagerly.
Null-appending results and the general builder growth policy are
unchanged.
---
arrow/array/booleanbuilder.go | 8 +-
arrow/array/builder.go | 14 +++
arrow/array/builder_append_nulls_benchmark_test.go | 63 +++++++++++
arrow/array/builder_append_nulls_test.go | 124 +++++++++++++++++++++
arrow/array/decimal.go | 8 +-
arrow/array/fixedsize_binarybuilder.go | 12 +-
arrow/array/float16_builder.go | 8 +-
arrow/array/interval.go | 24 +++-
arrow/array/numericbuilder.gen.go | 120 +++++++++++++++++---
arrow/array/numericbuilder.gen.go.tmpl | 8 +-
arrow/array/timestamp.go | 8 +-
11 files changed, 373 insertions(+), 24 deletions(-)
diff --git a/arrow/array/booleanbuilder.go b/arrow/array/booleanbuilder.go
index f90ac0b4..4b27462c 100644
--- a/arrow/array/booleanbuilder.go
+++ b/arrow/array/booleanbuilder.go
@@ -79,9 +79,15 @@ func (b *BooleanBuilder) AppendNull() {
}
func (b *BooleanBuilder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *BooleanBuilder) AppendEmptyValue() {
diff --git a/arrow/array/builder.go b/arrow/array/builder.go
index 2b5aa2cf..48113f43 100644
--- a/arrow/array/builder.go
+++ b/arrow/array/builder.go
@@ -355,6 +355,20 @@ func (b *builder) unsafeSetValid(length int) {
b.length = newLength
}
+func (b *builder) unsafeAppendNulls(n int) {
+ if n <= 0 {
+ return
+ }
+
+ if n == 1 {
+ bitutil.ClearBit(b.nullBitmap.Bytes(), b.length)
+ } else {
+ bitutil.SetBitsTo(b.nullBitmap.Bytes(), int64(b.length),
int64(n), false)
+ }
+ b.length += n
+ b.nulls += n
+}
+
func (b *builder) unsafeAppendEmptyValues(data []byte, valueSize, length int) {
if length <= 0 {
return
diff --git a/arrow/array/builder_append_nulls_benchmark_test.go
b/arrow/array/builder_append_nulls_benchmark_test.go
new file mode 100644
index 00000000..e334367c
--- /dev/null
+++ b/arrow/array/builder_append_nulls_benchmark_test.go
@@ -0,0 +1,63 @@
+// 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"
+)
+
+func BenchmarkFixedWidthBuilderAppendNulls(b *testing.B) {
+ builders := []struct {
+ name string
+ new func(memory.Allocator) array.Builder
+ }{
+ {"int32", func(mem memory.Allocator) array.Builder { return
array.NewInt32Builder(mem) }},
+ {"int64", func(mem memory.Allocator) array.Builder { return
array.NewInt64Builder(mem) }},
+ {"decimal128", func(mem memory.Allocator) array.Builder {
+ return array.NewDecimal128Builder(mem,
&arrow.Decimal128Type{Precision: 38})
+ }},
+ {"timestamp", func(mem memory.Allocator) array.Builder {
+ return array.NewTimestampBuilder(mem,
&arrow.TimestampType{Unit: arrow.Nanosecond})
+ }},
+ {"boolean", func(mem memory.Allocator) array.Builder { return
array.NewBooleanBuilder(mem) }},
+ {"fixed-size-binary", func(mem memory.Allocator) array.Builder {
+ return array.NewFixedSizeBinaryBuilder(mem,
&arrow.FixedSizeBinaryType{ByteWidth: 16})
+ }},
+ }
+
+ for _, builder := range builders {
+ b.Run(builder.name, func(b *testing.B) {
+ for _, count := range []int{1, 8, 64, 1024, 65536} {
+ b.Run(fmt.Sprintf("count-%d", count), func(b
*testing.B) {
+ mem := memory.NewGoAllocator()
+ b.ReportAllocs()
+ b.ResetTimer()
+ for b.Loop() {
+ instance := builder.new(mem)
+ instance.AppendNulls(count)
+ instance.Release()
+ }
+ })
+ }
+ })
+ }
+}
diff --git a/arrow/array/builder_append_nulls_test.go
b/arrow/array/builder_append_nulls_test.go
new file mode 100644
index 00000000..d6eb9236
--- /dev/null
+++ b/arrow/array/builder_append_nulls_test.go
@@ -0,0 +1,124 @@
+// 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 TestFixedWidthBuilderAppendNulls(t *testing.T) {
+ builders := []struct {
+ name string
+ new func(memory.Allocator) array.Builder
+ }{
+ {"int32", func(mem memory.Allocator) array.Builder { return
array.NewInt32Builder(mem) }},
+ {"float16", func(mem memory.Allocator) array.Builder { return
array.NewFloat16Builder(mem) }},
+ {"decimal128", func(mem memory.Allocator) array.Builder {
+ return array.NewDecimal128Builder(mem,
&arrow.Decimal128Type{Precision: 38})
+ }},
+ {"timestamp", func(mem memory.Allocator) array.Builder {
+ return array.NewTimestampBuilder(mem,
&arrow.TimestampType{Unit: arrow.Nanosecond})
+ }},
+ {"month-interval", func(mem memory.Allocator) array.Builder {
return array.NewMonthIntervalBuilder(mem) }},
+ {"day-time-interval", func(mem memory.Allocator) array.Builder
{ return array.NewDayTimeIntervalBuilder(mem) }},
+ {"month-day-nano-interval", func(mem memory.Allocator)
array.Builder {
+ return array.NewMonthDayNanoIntervalBuilder(mem)
+ }},
+ {"boolean", func(mem memory.Allocator) array.Builder { return
array.NewBooleanBuilder(mem) }},
+ {"fixed-size-binary", func(mem memory.Allocator) array.Builder {
+ return array.NewFixedSizeBinaryBuilder(mem,
&arrow.FixedSizeBinaryType{ByteWidth: 16})
+ }},
+ }
+
+ for _, builder := range builders {
+ t.Run(builder.name, func(t *testing.T) {
+ mem :=
memory.NewCheckedAllocator(memory.NewGoAllocator())
+ defer mem.AssertSize(t, 0)
+
+ instance := builder.new(mem)
+ defer instance.Release()
+
+ instance.AppendNulls(0)
+ instance.AppendNulls(-1)
+ require.Zero(t, instance.Len())
+ require.Zero(t, instance.Cap())
+ require.Zero(t, instance.NullN())
+
+ instance.AppendEmptyValue()
+ instance.AppendNulls(10)
+ instance.AppendEmptyValue()
+ require.Equal(t, 12, instance.Len())
+ require.Equal(t, 10, instance.NullN())
+
+ result := instance.NewArray()
+ defer result.Release()
+ require.True(t, result.IsValid(0))
+ for idx := 1; idx <= 10; idx++ {
+ require.True(t, result.IsNull(idx))
+ }
+ require.True(t, result.IsValid(11))
+ })
+ }
+}
+
+func TestFixedWidthBuilderAppendNullsAfterTruncate(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+ defer mem.AssertSize(t, 0)
+
+ builder := array.NewInt32Builder(mem)
+ defer builder.Release()
+ builder.AppendEmptyValues(16)
+ builder.Resize(3)
+ builder.AppendNulls(10)
+
+ result := builder.NewInt32Array()
+ defer result.Release()
+ require.Equal(t, 13, result.Len())
+ require.Equal(t, 10, result.NullN())
+ for idx := 0; idx < 3; idx++ {
+ require.True(t, result.IsValid(idx))
+ }
+ for idx := 3; idx < result.Len(); idx++ {
+ require.True(t, result.IsNull(idx))
+ }
+}
+
+func TestFixedSizeBinaryBuilderAppendNullsAcrossGrowthBoundary(t *testing.T) {
+ const (
+ count = 1 << 16
+ byteSize = 16
+ )
+
+ mem := memory.NewCheckedAllocator(memory.NewGoAllocator())
+ defer mem.AssertSize(t, 0)
+
+ builder := array.NewFixedSizeBinaryBuilder(mem,
&arrow.FixedSizeBinaryType{ByteWidth: byteSize})
+ builder.AppendNulls(count)
+ builder.AppendNulls(count)
+
+ result := builder.NewFixedSizeBinaryArray()
+ require.Equal(t, count*2, result.Len())
+ require.Equal(t, count*2, result.NullN())
+ require.Equal(t, count*2*byteSize, result.Data().Buffers()[1].Cap())
+ result.Release()
+ builder.Release()
+}
diff --git a/arrow/array/decimal.go b/arrow/array/decimal.go
index d284e2fa..92d049f4 100644
--- a/arrow/array/decimal.go
+++ b/arrow/array/decimal.go
@@ -232,9 +232,15 @@ func (b *baseDecimalBuilder[T]) AppendNull() {
}
func (b *baseDecimalBuilder[T]) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *baseDecimalBuilder[T]) AppendEmptyValue() {
diff --git a/arrow/array/fixedsize_binarybuilder.go
b/arrow/array/fixedsize_binarybuilder.go
index b077f6d9..9721b547 100644
--- a/arrow/array/fixedsize_binarybuilder.go
+++ b/arrow/array/fixedsize_binarybuilder.go
@@ -84,9 +84,16 @@ func (b *FixedSizeBinaryBuilder) AppendNull() {
}
func (b *FixedSizeBinaryBuilder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.values.Advance(n * b.dtype.ByteWidth)
+ b.unsafeAppendNulls(n)
}
func (b *FixedSizeBinaryBuilder) AppendEmptyValue() {
@@ -155,6 +162,9 @@ func (b *FixedSizeBinaryBuilder) Reserve(n int) {
// additional memory will be allocated. If n is smaller, the allocated memory
may reduced.
func (b *FixedSizeBinaryBuilder) Resize(n int) {
b.resize(n, b.init)
+ // Keep the value buffer sized to the effective builder capacity.
b.resize
+ // may clamp n to minBuilderCapacity.
+ b.values.resize(b.capacity * b.dtype.ByteWidth)
}
func (b *FixedSizeBinaryBuilder) truncate(n int) {
diff --git a/arrow/array/float16_builder.go b/arrow/array/float16_builder.go
index 2c198f5d..2b334b4e 100644
--- a/arrow/array/float16_builder.go
+++ b/arrow/array/float16_builder.go
@@ -80,9 +80,15 @@ func (b *Float16Builder) AppendNull() {
}
func (b *Float16Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Float16Builder) AppendEmptyValue() {
diff --git a/arrow/array/interval.go b/arrow/array/interval.go
index 61e9b8fd..df2eaf0e 100644
--- a/arrow/array/interval.go
+++ b/arrow/array/interval.go
@@ -183,9 +183,15 @@ func (b *MonthIntervalBuilder) AppendNull() {
}
func (b *MonthIntervalBuilder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *MonthIntervalBuilder) AppendEmptyValue() {
@@ -499,9 +505,15 @@ func (b *DayTimeIntervalBuilder) AppendNull() {
}
func (b *DayTimeIntervalBuilder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *DayTimeIntervalBuilder) AppendEmptyValue() {
@@ -818,9 +830,15 @@ func (b *MonthDayNanoIntervalBuilder) AppendNull() {
}
func (b *MonthDayNanoIntervalBuilder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *MonthDayNanoIntervalBuilder) AppendEmptyValue() {
diff --git a/arrow/array/numericbuilder.gen.go
b/arrow/array/numericbuilder.gen.go
index b189ba51..93dda6a5 100644
--- a/arrow/array/numericbuilder.gen.go
+++ b/arrow/array/numericbuilder.gen.go
@@ -78,9 +78,15 @@ func (b *Int64Builder) AppendNull() {
}
func (b *Int64Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Int64Builder) AppendEmptyValue() {
@@ -408,9 +414,15 @@ func (b *Uint64Builder) AppendNull() {
}
func (b *Uint64Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Uint64Builder) AppendEmptyValue() {
@@ -742,9 +754,15 @@ func (b *Float64Builder) AppendNull() {
}
func (b *Float64Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Float64Builder) AppendEmptyValue() {
@@ -990,9 +1008,15 @@ func (b *Int32Builder) AppendNull() {
}
func (b *Int32Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Int32Builder) AppendEmptyValue() {
@@ -1304,9 +1328,15 @@ func (b *Uint32Builder) AppendNull() {
}
func (b *Uint32Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Uint32Builder) AppendEmptyValue() {
@@ -1618,9 +1648,15 @@ func (b *Float32Builder) AppendNull() {
}
func (b *Float32Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Float32Builder) AppendEmptyValue() {
@@ -1866,9 +1902,15 @@ func (b *Int16Builder) AppendNull() {
}
func (b *Int16Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Int16Builder) AppendEmptyValue() {
@@ -2180,9 +2222,15 @@ func (b *Uint16Builder) AppendNull() {
}
func (b *Uint16Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Uint16Builder) AppendEmptyValue() {
@@ -2494,9 +2542,15 @@ func (b *Int8Builder) AppendNull() {
}
func (b *Int8Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Int8Builder) AppendEmptyValue() {
@@ -2808,9 +2862,15 @@ func (b *Uint8Builder) AppendNull() {
}
func (b *Uint8Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Uint8Builder) AppendEmptyValue() {
@@ -3123,9 +3183,15 @@ func (b *Time32Builder) AppendNull() {
}
func (b *Time32Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Time32Builder) AppendEmptyValue() {
@@ -3389,9 +3455,15 @@ func (b *Time64Builder) AppendNull() {
}
func (b *Time64Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Time64Builder) AppendEmptyValue() {
@@ -3645,9 +3717,15 @@ func (b *Date32Builder) AppendNull() {
}
func (b *Date32Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Date32Builder) AppendEmptyValue() {
@@ -3910,9 +3988,15 @@ func (b *Date64Builder) AppendNull() {
}
func (b *Date64Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *Date64Builder) AppendEmptyValue() {
@@ -4167,9 +4251,15 @@ func (b *DurationBuilder) AppendNull() {
}
func (b *DurationBuilder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *DurationBuilder) AppendEmptyValue() {
diff --git a/arrow/array/numericbuilder.gen.go.tmpl
b/arrow/array/numericbuilder.gen.go.tmpl
index 0a25279c..5fd59c7a 100644
--- a/arrow/array/numericbuilder.gen.go.tmpl
+++ b/arrow/array/numericbuilder.gen.go.tmpl
@@ -88,9 +88,15 @@ func (b *{{.Name}}Builder) AppendNull() {
}
func (b *{{.Name}}Builder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *{{.Name}}Builder) AppendEmptyValue() {
diff --git a/arrow/array/timestamp.go b/arrow/array/timestamp.go
index 1b17c3e2..fcb78366 100644
--- a/arrow/array/timestamp.go
+++ b/arrow/array/timestamp.go
@@ -197,9 +197,15 @@ func (b *TimestampBuilder) AppendNull() {
}
func (b *TimestampBuilder) AppendNulls(n int) {
- for i := 0; i < n; i++ {
+ if n <= 0 {
+ return
+ }
+ if n == 1 {
b.AppendNull()
+ return
}
+ b.Reserve(n)
+ b.unsafeAppendNulls(n)
}
func (b *TimestampBuilder) AppendEmptyValue() {