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 497a462d perf(compute): vectorize numeric to boolean casts (#1282)
497a462d is described below

commit 497a462dea0fe412c3f4fee6a47be5ab62dc0df2
Author: Minh Vu <[email protected]>
AuthorDate: Thu Sep 3 18:06:46 2026 +0200

    perf(compute): vectorize numeric to boolean casts (#1282)
    
    ## What does this PR do?
    
    - Adds an ARM64 NEON fast path for numeric to boolean casts.
    - Reuses the existing NEON `array != scalar` comparison kernel to
    produce the output bitmap.
    - Covers int32, uint32, int64, uint64, float32, and float64.
    - Keeps the scalar path for narrow integers, noasm builds, unsupported
    CPUs, and appengine.
    - Adds coverage for NaN, signed zero, infinities, sliced input, and
    bitmap tails.
    - Adds a benchmark matrix for the supported types, sizes, and zero
    distributions.
    
    ## Benchmark
    
    Apple M1 Pro, `GOMAXPROCS=1`, compared with `-tags noasm`.
    
    - 32-bit types: about 9x faster at 65K values and about 14x faster at 1M
    values.
    - 64-bit types: about 6x faster at 65K values and about 8x faster at 1M
    values.
    - Small arrays keep the same allocation count and retain the scalar tail
    path.
    
    ## Tests
    
    - `go test ./arrow/compute ./arrow/compute/internal/kernels`
    - `go test -tags noasm ./arrow/compute ./arrow/compute/internal/kernels`
    - amd64 compile check for the compute package noasm fallback
---
 arrow/compute/boolean_cast_bench_test.go           | 84 ++++++++++++++++++++++
 arrow/compute/cast_test.go                         | 40 +++++++++++
 arrow/compute/internal/kernels/boolean_cast.go     | 18 +++--
 .../internal/kernels/boolean_cast_neon_arm64.go    | 47 ++++++++++++
 .../compute/internal/kernels/boolean_cast_noasm.go | 28 ++++++++
 5 files changed, 211 insertions(+), 6 deletions(-)

diff --git a/arrow/compute/boolean_cast_bench_test.go 
b/arrow/compute/boolean_cast_bench_test.go
new file mode 100644
index 00000000..7c4e2873
--- /dev/null
+++ b/arrow/compute/boolean_cast_bench_test.go
@@ -0,0 +1,84 @@
+// 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"
+       "strconv"
+       "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 newBooleanCastBenchmarkArray(mem memory.Allocator, typ arrow.DataType, 
size int, zeroFraction float64) arrow.Array {
+       builder := array.NewBuilder(mem, typ)
+       builder.Reserve(size)
+       for i := 0; i < size; i++ {
+               if float64(i)/float64(size) < zeroFraction {
+                       if err := builder.AppendValueFromString("0"); err != 
nil {
+                               panic(err)
+                       }
+               } else if err := builder.AppendValueFromString("1"); err != nil 
{
+                       panic(err)
+               }
+       }
+       result := builder.NewArray()
+       builder.Release()
+       return result
+}
+
+func BenchmarkNumericToBoolCast(b *testing.B) {
+       for _, typ := range []arrow.DataType{
+               arrow.PrimitiveTypes.Int32,
+               arrow.PrimitiveTypes.Uint32,
+               arrow.PrimitiveTypes.Int64,
+               arrow.PrimitiveTypes.Uint64,
+               arrow.PrimitiveTypes.Float32,
+               arrow.PrimitiveTypes.Float64,
+       } {
+               width := int64(typ.(arrow.FixedWidthDataType).Bytes())
+               for _, size := range []int{64, 1024, 65536, 1_000_000} {
+                       for _, zeroFraction := range []float64{0, 0.5, 1} {
+                               b.Run(fmt.Sprintf("type=%s/size=%d/zeros=%s", 
typ, size, strconv.FormatFloat(zeroFraction, 'f', -1, 64)), func(b *testing.B) {
+                                       mem := memory.NewGoAllocator()
+                                       input := 
newBooleanCastBenchmarkArray(mem, typ, size, zeroFraction)
+                                       defer input.Release()
+                                       opts := compute.DefaultCastOptions(true)
+                                       opts.ToType = 
arrow.FixedWidthTypes.Boolean
+                                       ctx := context.Background()
+
+                                       b.ReportAllocs()
+                                       b.SetBytes(int64(size) * width)
+                                       b.ResetTimer()
+                                       for i := 0; i < b.N; i++ {
+                                               output, err := 
compute.CastArray(ctx, input, opts)
+                                               if err != nil {
+                                                       b.Fatal(err)
+                                               }
+                                               output.Release()
+                                       }
+                               })
+                       }
+               }
+       }
+}
diff --git a/arrow/compute/cast_test.go b/arrow/compute/cast_test.go
index ae0229a3..94805db3 100644
--- a/arrow/compute/cast_test.go
+++ b/arrow/compute/cast_test.go
@@ -466,6 +466,46 @@ func (c *CastSuite) TestNumericToBool() {
        }
 }
 
+func (c *CastSuite) TestNumericToBoolSpecialValues() {
+       builder := array.NewFloat64Builder(c.mem)
+       builder.AppendValues([]float64{
+               1,
+               math.Copysign(0, -1),
+               0,
+               math.NaN(),
+               math.Inf(1),
+               math.Inf(-1),
+               1,
+               -1,
+               0,
+               1,
+               math.Copysign(0, -1),
+               math.NaN(),
+               math.Inf(1),
+               math.Inf(-1),
+               0,
+               -1,
+               1,
+       }, nil)
+       input := builder.NewArray()
+       builder.Release()
+       defer input.Release()
+
+       sliced := array.NewSlice(input, 1, 16)
+       defer sliced.Release()
+
+       expectedBuilder := array.NewBooleanBuilder(c.mem)
+       expectedBuilder.AppendValues([]bool{
+               false, false, true, true, true, true, true, false,
+               true, false, true, true, true, false, true,
+       }, nil)
+       expected := expectedBuilder.NewArray()
+       expectedBuilder.Release()
+       defer expected.Release()
+
+       checkCast(c.T(), sliced, expected, *compute.DefaultCastOptions(true))
+}
+
 func (c *CastSuite) StringToBool() {
        for _, dt := range []arrow.DataType{arrow.BinaryTypes.String, 
arrow.BinaryTypes.LargeString} {
                c.checkCast(dt, arrow.FixedWidthTypes.Boolean,
diff --git a/arrow/compute/internal/kernels/boolean_cast.go 
b/arrow/compute/internal/kernels/boolean_cast.go
index dbe96f10..4232b0df 100644
--- a/arrow/compute/internal/kernels/boolean_cast.go
+++ b/arrow/compute/internal/kernels/boolean_cast.go
@@ -35,6 +35,12 @@ func isNonZero[T arrow.FixedWidthType](ctx *exec.KernelCtx, 
in []T, out []byte)
        return nil
 }
 
+func numericToBoolKernel[T arrow.NumericType](typ arrow.Type) 
func(*exec.KernelCtx, []T, []byte) error {
+       return func(ctx *exec.KernelCtx, in []T, out []byte) error {
+               return numericToBoolNeon(typ, ctx, in, out)
+       }
+}
+
 // GetBooleanCastKernels returns the slice of scalar kernels for casting
 // values *to* a boolean type.
 func GetBooleanCastKernels() []exec.ScalarKernel {
@@ -55,17 +61,17 @@ func GetBooleanCastKernels() []exec.ScalarKernel {
                case arrow.UINT16:
                        ex = ScalarUnaryBoolOutput(isNonZero[uint16])
                case arrow.INT32:
-                       ex = ScalarUnaryBoolOutput(isNonZero[int32])
+                       ex = 
ScalarUnaryBoolOutput(numericToBoolKernel[int32](arrow.INT32))
                case arrow.UINT32:
-                       ex = ScalarUnaryBoolOutput(isNonZero[uint32])
+                       ex = 
ScalarUnaryBoolOutput(numericToBoolKernel[uint32](arrow.UINT32))
                case arrow.INT64:
-                       ex = ScalarUnaryBoolOutput(isNonZero[int64])
+                       ex = 
ScalarUnaryBoolOutput(numericToBoolKernel[int64](arrow.INT64))
                case arrow.UINT64:
-                       ex = ScalarUnaryBoolOutput(isNonZero[uint64])
+                       ex = 
ScalarUnaryBoolOutput(numericToBoolKernel[uint64](arrow.UINT64))
                case arrow.FLOAT32:
-                       ex = ScalarUnaryBoolOutput(isNonZero[float32])
+                       ex = 
ScalarUnaryBoolOutput(numericToBoolKernel[float32](arrow.FLOAT32))
                case arrow.FLOAT64:
-                       ex = ScalarUnaryBoolOutput(isNonZero[float64])
+                       ex = 
ScalarUnaryBoolOutput(numericToBoolKernel[float64](arrow.FLOAT64))
                }
                k := exec.NewScalarKernel(
                        []exec.InputType{exec.NewExactInput(ty)}, out, ex, nil)
diff --git a/arrow/compute/internal/kernels/boolean_cast_neon_arm64.go 
b/arrow/compute/internal/kernels/boolean_cast_neon_arm64.go
new file mode 100644
index 00000000..78211f29
--- /dev/null
+++ b/arrow/compute/internal/kernels/boolean_cast_neon_arm64.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.
+
+//go:build go1.18 && arm64 && !noasm && !appengine
+
+package kernels
+
+import (
+       "unsafe"
+
+       "github.com/apache/arrow-go/v18/arrow"
+       "github.com/apache/arrow-go/v18/arrow/bitutil"
+       "github.com/apache/arrow-go/v18/arrow/compute/exec"
+       "golang.org/x/sys/cpu"
+)
+
+func numericToBoolNeon[T arrow.NumericType](typ arrow.Type, ctx 
*exec.KernelCtx, in []T, out []byte) error {
+       if !cpu.ARM64.HasASIMD {
+               return isNonZero(ctx, in, out)
+       }
+
+       var zero T
+       bulk := len(in) &^ 7
+       if bulk != 0 {
+               left := arrow.GetBytes(in[:bulk])
+               right := unsafe.Slice((*byte)(unsafe.Pointer(&zero)), 
int(unsafe.Sizeof(zero)))
+               _comparison_neon(int(typ), int(CmpNE), neonCompareArrayScalar,
+                       unsafe.Pointer(&left[0]), unsafe.Pointer(&right[0]), 
unsafe.Pointer(&out[0]), int64(bulk/8))
+       }
+       for i, v := range in[bulk:] {
+               bitutil.SetBitTo(out, bulk+i, v != zero)
+       }
+       return nil
+}
diff --git a/arrow/compute/internal/kernels/boolean_cast_noasm.go 
b/arrow/compute/internal/kernels/boolean_cast_noasm.go
new file mode 100644
index 00000000..2bdd800c
--- /dev/null
+++ b/arrow/compute/internal/kernels/boolean_cast_noasm.go
@@ -0,0 +1,28 @@
+// 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 && (noasm || !arm64 || appengine)
+
+package kernels
+
+import (
+       "github.com/apache/arrow-go/v18/arrow"
+       "github.com/apache/arrow-go/v18/arrow/compute/exec"
+)
+
+func numericToBoolNeon[T arrow.NumericType](_ arrow.Type, ctx *exec.KernelCtx, 
in []T, out []byte) error {
+       return isNonZero(ctx, in, out)
+}

Reply via email to