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 f92c33ad perf(compute): add ARM64 NEON constant multiplication (#1268)
f92c33ad is described below
commit f92c33ad58813c7fa88079b3142be0c84cb12241
Author: Minh Vu <[email protected]>
AuthorDate: Thu Sep 3 18:03:20 2026 +0200
perf(compute): add ARM64 NEON constant multiplication (#1268)
This adds an ARM64 NEON fast path for constant multiplication in the
compute kernels.
**What changed**
- adds NEON kernels for int32/int64 input and output combinations
- keeps the existing generic implementation when NEON is unavailable
- adds boundary and overflow correctness coverage for short and
non-multiple-of-four inputs
- adds benchmarks for all four conversion combinations
**Benchmark**
Apple M1 Pro, darwin/arm64, 1 MiB input:
| operation | generic | NEON dispatch | speedup |
| --- | ---: | ---: | ---: |
| int32 -> int32 | 421,874 ns/op | 87,556 ns/op | 4.82x |
| int32 -> int64 | 423,170 ns/op | 296,760 ns/op | 1.43x |
| int64 -> int32 | 422,409 ns/op | 135,615 ns/op | 3.11x |
| int64 -> int64 | 438,196 ns/op | 302,779 ns/op | 1.45x |
**Tests**
- go test -count=1 ./arrow/compute/internal/kernels
- go test -run ^$ -bench ^BenchmarkMultiplyConstant$ -benchmem -count=1
./arrow/compute/internal/kernels
---
arrow/compute/internal/kernels/Makefile | 6 +-
.../internal/kernels/_lib/constant_factor_neon.c | 108 +++++++++
.../internal/kernels/_lib/constant_factor_neon.s | 189 ++++++++++++++++
.../internal/kernels/constant_factor_neon_arm64.go | 99 +++++++++
.../internal/kernels/constant_factor_neon_arm64.s | 218 +++++++++++++++++++
.../kernels/constant_factor_neon_arm64_native.go | 33 +++
.../kernels/constant_factor_neon_arm64_test.go | 43 ++++
.../internal/kernels/constant_factor_test.go | 241 +++++++++++++++++++++
8 files changed, 935 insertions(+), 2 deletions(-)
diff --git a/arrow/compute/internal/kernels/Makefile
b/arrow/compute/internal/kernels/Makefile
index 1edf1270..91a12e62 100644
--- a/arrow/compute/internal/kernels/Makefile
+++ b/arrow/compute/internal/kernels/Makefile
@@ -30,6 +30,8 @@ ASM_FLAGS_POPCNT=-mpopcnt
C_FLAGS_NEON=-O3 -fvectorize -mllvm -force-vector-width=16
-fno-asynchronous-unwind-tables -mno-red-zone -mstackrealign -fno-exceptions \
-fno-rtti -fno-builtin -fno-math-errno -funsafe-math-optimizations
-fno-rounding-math -fno-trapping-math -fno-jump-tables -I_lib
-I../../../../internal/utils/_lib
+C_FLAGS_NEON_CONSTANT_FACTOR=-O2 -fno-unroll-loops -fno-vectorize
-fno-slp-vectorize -fno-asynchronous-unwind-tables -mno-red-zone
-fno-exceptions \
+ -fno-rtti -fno-builtin -fno-math-errno -funsafe-math-optimizations
-fno-rounding-math -fno-trapping-math -fno-jump-tables -I_lib
-I../../../../internal/utils/_lib
GO_SOURCES := $(shell find . -path ./_lib -prune -o -name '*.go' -not -name
'*_test.go')
ALL_SOURCES := $(shell find . -path ./_lib -prune -o -name '*.go' -name '*.s'
-not -name '*_test.go')
@@ -82,8 +84,8 @@ _lib/constant_factor_avx2_amd64.s: _lib/constant_factor.c
_lib/constant_factor_sse4_amd64.s: _lib/constant_factor.c
$(CC) -S $(C_FLAGS) $(ASM_FLAGS_SSE4) $^ -o $@ ; $(PERL_FIXUP_ROTATE) $@
-_lib/constant_factor_neon.s: _lib/constant_factor.c
- $(CC) -S $(C_FLAGS_NEON) $^ -o $@ ; $(PERL_FIXUP_ROTATE) $@
+_lib/constant_factor_neon.s: _lib/constant_factor_neon.c
+ $(CC) -S $(C_FLAGS_NEON_CONSTANT_FACTOR) $^ -o $@ ;
$(PERL_FIXUP_ROTATE) $@
cast_numeric_avx2_amd64.s: _lib/cast_numeric_avx2_amd64.s
$(C2GOASM) -a -f $^ $@
diff --git a/arrow/compute/internal/kernels/_lib/constant_factor_neon.c
b/arrow/compute/internal/kernels/_lib/constant_factor_neon.c
new file mode 100644
index 00000000..78b4bc6d
--- /dev/null
+++ b/arrow/compute/internal/kernels/_lib/constant_factor_neon.c
@@ -0,0 +1,108 @@
+// 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.
+
+#include <arm_neon.h>
+#include <stdint.h>
+
+static inline uint64x2_t multiply_u64x2_by_u64(const uint64x2_t values,
+ const uint64_t factor) {
+ const uint32x4_t values32 = vreinterpretq_u32_u64(values);
+ const uint32x2_t values_lo =
+ vuzp1_u32(vget_low_u32(values32), vget_high_u32(values32));
+ const uint32x2_t values_hi =
+ vuzp2_u32(vget_low_u32(values32), vget_high_u32(values32));
+ const uint32x2_t factor_lo = vdup_n_u32((uint32_t)factor);
+ const uint32x2_t factor_hi = vdup_n_u32((uint32_t)(factor >> 32));
+ const uint64x2_t low = vmull_u32(values_lo, factor_lo);
+ const uint64x2_t cross =
+ vmlal_u32(vmull_u32(values_lo, factor_hi), values_hi, factor_lo);
+
+ return vaddq_u64(low, vshlq_n_u64(cross, 32));
+}
+
+void multiply_constant_int32_int32_neon(const int32_t* src, int32_t* dest,
+ const int len, const int64_t factor) {
+ int i = 0;
+ const uint32x4_t factor_vec = vdupq_n_u32((uint32_t)factor);
+
+ for (; i + 4 <= len; i += 4) {
+ const uint32x4_t values = vld1q_u32((const uint32_t*)(src + i));
+ vst1q_u32((uint32_t*)(dest + i), vmulq_u32(values, factor_vec));
+ }
+
+ for (; i < len; ++i) {
+ dest[i] = (int32_t)((uint32_t)src[i] * (uint32_t)factor);
+ }
+}
+
+void multiply_constant_int32_int64_neon(const int32_t* src, int64_t* dest,
+ const int len, const int64_t factor) {
+ int i = 0;
+
+ for (; i + 4 <= len; i += 4) {
+ const int32x4_t values = vld1q_s32(src + i);
+ const uint64x2_t low =
+ vreinterpretq_u64_s64(vmovl_s32(vget_low_s32(values)));
+ const uint64x2_t high =
+ vreinterpretq_u64_s64(vmovl_s32(vget_high_s32(values)));
+ vst1q_u64((uint64_t*)(dest + i), multiply_u64x2_by_u64(low, factor));
+ vst1q_u64((uint64_t*)(dest + i + 2),
+ multiply_u64x2_by_u64(high, factor));
+ }
+
+ for (; i < len; ++i) {
+ dest[i] = (int64_t)((uint64_t)(int64_t)src[i] * (uint64_t)factor);
+ }
+}
+
+void multiply_constant_int64_int32_neon(const int64_t* src, int32_t* dest,
+ const int len, const int64_t factor) {
+ int i = 0;
+ const uint32x4_t factor_vec = vdupq_n_u32((uint32_t)factor);
+
+ for (; i + 4 <= len; i += 4) {
+ const uint64x2_t low_values =
+ vld1q_u64((const uint64_t*)(src + i));
+ const uint64x2_t high_values =
+ vld1q_u64((const uint64_t*)(src + i + 2));
+ const uint32x4_t low_values32 = vreinterpretq_u32_u64(low_values);
+ const uint32x4_t high_values32 = vreinterpretq_u32_u64(high_values);
+ const uint32x2_t low =
+ vuzp1_u32(vget_low_u32(low_values32), vget_high_u32(low_values32));
+ const uint32x2_t high = vuzp1_u32(vget_low_u32(high_values32),
+ vget_high_u32(high_values32));
+ const uint32x4_t values = vcombine_u32(low, high);
+ vst1q_u32((uint32_t*)(dest + i), vmulq_u32(values, factor_vec));
+ }
+
+ for (; i < len; ++i) {
+ dest[i] = (int32_t)((uint32_t)src[i] * (uint32_t)factor);
+ }
+}
+
+void multiply_constant_int64_int64_neon(const int64_t* src, int64_t* dest,
+ const int len, const int64_t factor) {
+ int i = 0;
+
+ for (; i + 2 <= len; i += 2) {
+ const uint64x2_t values = vld1q_u64((const uint64_t*)(src + i));
+ vst1q_u64((uint64_t*)(dest + i), multiply_u64x2_by_u64(values,
factor));
+ }
+
+ for (; i < len; ++i) {
+ dest[i] = (int64_t)((uint64_t)src[i] * (uint64_t)factor);
+ }
+}
diff --git a/arrow/compute/internal/kernels/_lib/constant_factor_neon.s
b/arrow/compute/internal/kernels/_lib/constant_factor_neon.s
new file mode 100644
index 00000000..f9f4ea53
--- /dev/null
+++ b/arrow/compute/internal/kernels/_lib/constant_factor_neon.s
@@ -0,0 +1,189 @@
+ .build_version macos, 26, 0 sdk_version 26, 5
+ .section __TEXT,__text,regular,pure_instructions
+ .globl _multiply_constant_int32_int32_neon ; -- Begin function
multiply_constant_int32_int32_neon
+ .p2align 2
+_multiply_constant_int32_int32_neon: ; @multiply_constant_int32_int32_neon
+; %bb.0:
+ cmp w2, #4
+ b.ge LBB0_2
+; %bb.1:
+ mov w8, #0 ; =0x0
+ cmp w8, w2
+ b.lt LBB0_5
+ b LBB0_7
+LBB0_2:
+ fmov s0, w3
+ mov w8, #4 ; =0x4
+ mov x9, x0
+ mov x10, x1
+ mov w11, w2
+LBB0_3: ; =>This Inner Loop Header: Depth=1
+ ldr q1, [x9], #16
+ mul.4s v1, v1, v0[0]
+ str q1, [x10], #16
+ add x8, x8, #4
+ cmp x8, x11
+ b.ls LBB0_3
+; %bb.4:
+ and w8, w2, #0x7ffffffc
+ cmp w8, w2
+ b.ge LBB0_7
+LBB0_5:
+ mov w8, w8
+LBB0_6: ; =>This Inner Loop Header: Depth=1
+ ldr w9, [x0, x8, lsl #2]
+ mul w9, w9, w3
+ str w9, [x1, x8, lsl #2]
+ add x8, x8, #1
+ cmp w2, w8
+ b.gt LBB0_6
+LBB0_7:
+ ret
+ ; -- End function
+ .globl _multiply_constant_int32_int64_neon ; -- Begin function
multiply_constant_int32_int64_neon
+ .p2align 2
+_multiply_constant_int32_int64_neon: ; @multiply_constant_int32_int64_neon
+; %bb.0:
+ cmp w2, #4
+ b.ge LBB1_2
+; %bb.1:
+ mov w8, #0 ; =0x0
+ cmp w8, w2
+ b.lt LBB1_5
+ b LBB1_7
+LBB1_2:
+ fmov s0, w3
+ lsr x8, x3, #32
+ fmov s1, w8
+ mov w8, w2
+ add x9, x1, #16
+ mov w10, #4 ; =0x4
+ mov x11, x0
+LBB1_3: ; =>This Inner Loop Header: Depth=1
+ ldr q2, [x11], #16
+ sshll.2d v3, v2, #0
+ sshll2.2d v4, v2, #0
+ ext.16b v5, v3, v3, #8
+ zip2.2s v3, v3, v5
+ umull.2d v3, v3, v0[0]
+ umlal.2d v3, v2, v1[0]
+ shl.2d v3, v3, #32
+ umlal.2d v3, v2, v0[0]
+ ext.16b v5, v4, v4, #8
+ zip2.2s v4, v4, v5
+ umull.2d v4, v4, v0[0]
+ umlal2.2d v4, v2, v1[0]
+ shl.2d v4, v4, #32
+ umlal2.2d v4, v2, v0[0]
+ stp q3, q4, [x9, #-16]
+ add x10, x10, #4
+ add x9, x9, #32
+ cmp x10, x8
+ b.ls LBB1_3
+; %bb.4:
+ and w8, w2, #0x7ffffffc
+ cmp w8, w2
+ b.ge LBB1_7
+LBB1_5:
+ mov w8, w8
+LBB1_6: ; =>This Inner Loop Header: Depth=1
+ ldrsw x9, [x0, x8, lsl #2]
+ mul x9, x3, x9
+ str x9, [x1, x8, lsl #3]
+ add x8, x8, #1
+ cmp w2, w8
+ b.gt LBB1_6
+LBB1_7:
+ ret
+ ; -- End function
+ .globl _multiply_constant_int64_int32_neon ; -- Begin function
multiply_constant_int64_int32_neon
+ .p2align 2
+_multiply_constant_int64_int32_neon: ; @multiply_constant_int64_int32_neon
+; %bb.0:
+ cmp w2, #4
+ b.ge LBB2_2
+; %bb.1:
+ mov w8, #0 ; =0x0
+ cmp w8, w2
+ b.lt LBB2_5
+ b LBB2_7
+LBB2_2:
+ fmov s0, w3
+ add x8, x0, #16
+ mov w9, #4 ; =0x4
+ mov x10, x1
+ mov w11, w2
+LBB2_3: ; =>This Inner Loop Header: Depth=1
+ ldp q1, q2, [x8, #-16]
+ uzp1.4s v1, v1, v2
+ mul.4s v1, v1, v0[0]
+ str q1, [x10], #16
+ add x9, x9, #4
+ add x8, x8, #32
+ cmp x9, x11
+ b.ls LBB2_3
+; %bb.4:
+ and w8, w2, #0x7ffffffc
+ cmp w8, w2
+ b.ge LBB2_7
+LBB2_5:
+ mov w8, w8
+LBB2_6: ; =>This Inner Loop Header: Depth=1
+ ldr x9, [x0, x8, lsl #3]
+ mul w9, w9, w3
+ str w9, [x1, x8, lsl #2]
+ add x8, x8, #1
+ cmp w2, w8
+ b.gt LBB2_6
+LBB2_7:
+ ret
+ ; -- End function
+ .globl _multiply_constant_int64_int64_neon ; -- Begin function
multiply_constant_int64_int64_neon
+ .p2align 2
+_multiply_constant_int64_int64_neon: ; @multiply_constant_int64_int64_neon
+; %bb.0:
+ cmp w2, #2
+ b.ge LBB3_2
+; %bb.1:
+ mov w8, #0 ; =0x0
+ cmp w8, w2
+ b.lt LBB3_5
+ b LBB3_7
+LBB3_2:
+ fmov s0, w3
+ lsr x8, x3, #32
+ fmov s1, w8
+ mov w8, w2
+ mov w9, #2 ; =0x2
+ mov x10, x0
+ mov x11, x1
+LBB3_3: ; =>This Inner Loop Header: Depth=1
+ ldr q2, [x10], #16
+ xtn.2s v3, v2
+ ext.16b v4, v2, v2, #8
+ zip2.2s v2, v2, v4
+ umull.2d v2, v2, v0[0]
+ umlal.2d v2, v3, v1[0]
+ shl.2d v2, v2, #32
+ umlal.2d v2, v3, v0[0]
+ str q2, [x11], #16
+ add x9, x9, #2
+ cmp x9, x8
+ b.ls LBB3_3
+; %bb.4:
+ and w8, w2, #0x7ffffffe
+ cmp w8, w2
+ b.ge LBB3_7
+LBB3_5:
+ mov w8, w8
+LBB3_6: ; =>This Inner Loop Header: Depth=1
+ ldr x9, [x0, x8, lsl #3]
+ mul x9, x9, x3
+ str x9, [x1, x8, lsl #3]
+ add x8, x8, #1
+ cmp w2, w8
+ b.gt LBB3_6
+LBB3_7:
+ ret
+ ; -- End function
+.subsections_via_symbols
diff --git a/arrow/compute/internal/kernels/constant_factor_neon_arm64.go
b/arrow/compute/internal/kernels/constant_factor_neon_arm64.go
new file mode 100644
index 00000000..0e8cc691
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_neon_arm64.go
@@ -0,0 +1,99 @@
+// 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 && !appengine
+
+package kernels
+
+import (
+ "math"
+ "unsafe"
+
+ "golang.org/x/sys/cpu"
+)
+
+// The generated assembly reads the length through a 32-bit register. Larger
+// Go slices must stay on the scalar path.
+const maxNeonLength = int(math.MaxInt32)
+
+func neonLengthFitsAssembly(length int) bool {
+ return length <= maxNeonLength
+}
+
+//go:noescape
+func _multiply_constant_int32_int32_neon(src, dest unsafe.Pointer, len int,
factor int64)
+
+func multiplyConstantInt32Int32Neon(in []int32, out []int32, factor int64) {
+ if len(out) == 0 {
+ return
+ }
+ if !neonLengthFitsAssembly(len(out)) {
+ multiplyConstantGo(in, out, factor)
+ return
+ }
+ _multiply_constant_int32_int32_neon(unsafe.Pointer(&in[0]),
unsafe.Pointer(&out[0]), len(out), factor)
+}
+
+//go:noescape
+func _multiply_constant_int32_int64_neon(src, dest unsafe.Pointer, len int,
factor int64)
+
+func multiplyConstantInt32Int64Neon(in []int32, out []int64, factor int64) {
+ if len(out) == 0 {
+ return
+ }
+ if !neonLengthFitsAssembly(len(out)) {
+ multiplyConstantGo(in, out, factor)
+ return
+ }
+ _multiply_constant_int32_int64_neon(unsafe.Pointer(&in[0]),
unsafe.Pointer(&out[0]), len(out), factor)
+}
+
+//go:noescape
+func _multiply_constant_int64_int32_neon(src, dest unsafe.Pointer, len int,
factor int64)
+
+func multiplyConstantInt64Int32Neon(in []int64, out []int32, factor int64) {
+ if len(out) == 0 {
+ return
+ }
+ if !neonLengthFitsAssembly(len(out)) {
+ multiplyConstantGo(in, out, factor)
+ return
+ }
+ _multiply_constant_int64_int32_neon(unsafe.Pointer(&in[0]),
unsafe.Pointer(&out[0]), len(out), factor)
+}
+
+//go:noescape
+func _multiply_constant_int64_int64_neon(src, dest unsafe.Pointer, len int,
factor int64)
+
+func multiplyConstantInt64Int64Neon(in []int64, out []int64, factor int64) {
+ if len(out) == 0 {
+ return
+ }
+ if !neonLengthFitsAssembly(len(out)) {
+ multiplyConstantGo(in, out, factor)
+ return
+ }
+ _multiply_constant_int64_int64_neon(unsafe.Pointer(&in[0]),
unsafe.Pointer(&out[0]), len(out), factor)
+}
+
+func init() {
+ if cpu.ARM64.HasASIMD {
+ multiplyConstantInt32Int32 = multiplyConstantInt32Int32Neon
+ multiplyConstantInt32Int64 = multiplyConstantInt32Int64Neon
+ multiplyConstantInt64Int32 = multiplyConstantInt64Int32Neon
+ multiplyConstantInt64Int64 = multiplyConstantInt64Int64Neon
+ }
+}
diff --git a/arrow/compute/internal/kernels/constant_factor_neon_arm64.s
b/arrow/compute/internal/kernels/constant_factor_neon_arm64.s
new file mode 100644
index 00000000..ac1a660d
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_neon_arm64.s
@@ -0,0 +1,218 @@
+//go:build go1.18 && !noasm && !appengine
+// AUTO-GENERATED BY C2GOASM -- DO NOT EDIT
+
+DATA gclocals·untyped(SB)/8, $1
+GLOBL gclocals·untyped(SB), 8, $8
+
+TEXT ·_c2goasm_native_multiply_constant_int32_int32_neon(SB), 516, $0-32
+
+ CMPW $4, R2 // cmp w2, #4
+ BGE multiply_constant_int32_int32_neon_LBB0_2
+ MOVW $0, R8 // mov w8, #0
+ CMPW R2, R8 // cmp w8, w2
+ BLT multiply_constant_int32_int32_neon_LBB0_5
+ B multiply_constant_int32_int32_neon_LBB0_7
+multiply_constant_int32_int32_neon_LBB0_2:
+ WORD $0x1e270060 // fmov s0, w3
+ MOVW $4, R8 // mov w8, #4
+ MOVD R0, R9 // mov x9, x0
+ MOVD R1, R10 // mov x10, x1
+ MOVWU R2, R11 // mov w11, w2
+multiply_constant_int32_int32_neon_LBB0_3:
+ WORD $0x3cc10521 // ldr q1, [x9], #16
+ WORD $0x4f808021 // mul.4s v1, v1, v0[0]
+ WORD $0x3c810541 // str q1, [x10], #16
+ ADD $4, R8, R8 // add x8, x8, #4
+ CMP R11, R8 // cmp x8, x11
+ BLS multiply_constant_int32_int32_neon_LBB0_3
+ ANDW $2147483644, R2, R8 // and w8, w2, #0x7ffffffc
+ CMPW R2, R8 // cmp w8, w2
+ BGE multiply_constant_int32_int32_neon_LBB0_7
+multiply_constant_int32_int32_neon_LBB0_5:
+ MOVWU R8, R8 // mov w8, w8
+multiply_constant_int32_int32_neon_LBB0_6:
+ MOVWU (R0)(R8<<2), R9 // ldr w9, [x0, x8, lsl #2]
+ MULW R3, R9, R9 // mul w9, w9, w3
+ MOVW R9, (R1)(R8<<2) // str w9, [x1, x8, lsl #2]
+ ADD $1, R8, R8 // add x8, x8, #1
+ CMPW R8, R2 // cmp w2, w8
+ BGT multiply_constant_int32_int32_neon_LBB0_6
+multiply_constant_int32_int32_neon_LBB0_7:
+ RET
+
+
+TEXT ·_multiply_constant_int32_int32_neon(SB), 4, $0-32
+
+ MOVD src+0(FP), R0
+ MOVD dest+8(FP), R1
+ MOVD len+16(FP), R2
+ MOVD factor+24(FP), R3
+ CALL ·_c2goasm_native_multiply_constant_int32_int32_neon(SB)
+ RET
+
+
+TEXT ·_c2goasm_native_multiply_constant_int32_int64_neon(SB), 516, $0-32
+
+ CMPW $4, R2 // cmp w2, #4
+ BGE multiply_constant_int32_int64_neon_LBB1_2
+ MOVW $0, R8 // mov w8, #0
+ CMPW R2, R8 // cmp w8, w2
+ BLT multiply_constant_int32_int64_neon_LBB1_5
+ B multiply_constant_int32_int64_neon_LBB1_7
+multiply_constant_int32_int64_neon_LBB1_2:
+ WORD $0x1e270060 // fmov s0, w3
+ LSR $32, R3, R8 // lsr x8, x3, #32
+ WORD $0x1e270101 // fmov s1, w8
+ MOVWU R2, R8 // mov w8, w2
+ ADD $16, R1, R9 // add x9, x1, #16
+ MOVW $4, R10 // mov w10, #4
+ MOVD R0, R11 // mov x11, x0
+multiply_constant_int32_int64_neon_LBB1_3:
+ WORD $0x3cc10562 // ldr q2, [x11], #16
+ WORD $0x0f20a443 // sshll.2d v3, v2, #0
+ WORD $0x4f20a444 // sshll2.2d v4, v2, #0
+ VEXT $8, V3.B16, V3.B16, V5.B16 // ext.16b v5, v3, v3, #8
+ VZIP2 V5.S2, V3.S2, V3.S2 // zip2.2s v3, v3, v5
+ WORD $0x2f80a063 // umull.2d v3, v3, v0[0]
+ WORD $0x2f812043 // umlal.2d v3, v2, v1[0]
+ VSHL $32, V3.D2, V3.D2 // shl.2d v3, v3, #32
+ WORD $0x2f802043 // umlal.2d v3, v2, v0[0]
+ VEXT $8, V4.B16, V4.B16, V5.B16 // ext.16b v5, v4, v4, #8
+ VZIP2 V5.S2, V4.S2, V4.S2 // zip2.2s v4, v4, v5
+ WORD $0x2f80a084 // umull.2d v4, v4, v0[0]
+ WORD $0x6f812044 // umlal2.2d v4, v2, v1[0]
+ VSHL $32, V4.D2, V4.D2 // shl.2d v4, v4, #32
+ WORD $0x6f802044 // umlal2.2d v4, v2, v0[0]
+ WORD $0xad3f9123 // stp q3, q4, [x9, #-16]
+ ADD $4, R10, R10 // add x10, x10, #4
+ ADD $32, R9, R9 // add x9, x9, #32
+ CMP R8, R10 // cmp x10, x8
+ BLS multiply_constant_int32_int64_neon_LBB1_3
+ ANDW $2147483644, R2, R8 // and w8, w2, #0x7ffffffc
+ CMPW R2, R8 // cmp w8, w2
+ BGE multiply_constant_int32_int64_neon_LBB1_7
+multiply_constant_int32_int64_neon_LBB1_5:
+ MOVWU R8, R8 // mov w8, w8
+multiply_constant_int32_int64_neon_LBB1_6:
+ MOVW (R0)(R8<<2), R9 // ldrsw x9, [x0, x8, lsl #2]
+ MUL R9, R3, R9 // mul x9, x3, x9
+ MOVD R9, (R1)(R8<<3) // str x9, [x1, x8, lsl #3]
+ ADD $1, R8, R8 // add x8, x8, #1
+ CMPW R8, R2 // cmp w2, w8
+ BGT multiply_constant_int32_int64_neon_LBB1_6
+multiply_constant_int32_int64_neon_LBB1_7:
+ RET
+
+
+TEXT ·_multiply_constant_int32_int64_neon(SB), 4, $0-32
+
+ MOVD src+0(FP), R0
+ MOVD dest+8(FP), R1
+ MOVD len+16(FP), R2
+ MOVD factor+24(FP), R3
+ CALL ·_c2goasm_native_multiply_constant_int32_int64_neon(SB)
+ RET
+
+
+TEXT ·_c2goasm_native_multiply_constant_int64_int32_neon(SB), 516, $0-32
+
+ CMPW $4, R2 // cmp w2, #4
+ BGE multiply_constant_int64_int32_neon_LBB2_2
+ MOVW $0, R8 // mov w8, #0
+ CMPW R2, R8 // cmp w8, w2
+ BLT multiply_constant_int64_int32_neon_LBB2_5
+ B multiply_constant_int64_int32_neon_LBB2_7
+multiply_constant_int64_int32_neon_LBB2_2:
+ WORD $0x1e270060 // fmov s0, w3
+ ADD $16, R0, R8 // add x8, x0, #16
+ MOVW $4, R9 // mov w9, #4
+ MOVD R1, R10 // mov x10, x1
+ MOVWU R2, R11 // mov w11, w2
+multiply_constant_int64_int32_neon_LBB2_3:
+ WORD $0xad7f8901 // ldp q1, q2, [x8, #-16]
+ VUZP1 V2.S4, V1.S4, V1.S4 // uzp1.4s v1, v1, v2
+ WORD $0x4f808021 // mul.4s v1, v1, v0[0]
+ WORD $0x3c810541 // str q1, [x10], #16
+ ADD $4, R9, R9 // add x9, x9, #4
+ ADD $32, R8, R8 // add x8, x8, #32
+ CMP R11, R9 // cmp x9, x11
+ BLS multiply_constant_int64_int32_neon_LBB2_3
+ ANDW $2147483644, R2, R8 // and w8, w2, #0x7ffffffc
+ CMPW R2, R8 // cmp w8, w2
+ BGE multiply_constant_int64_int32_neon_LBB2_7
+multiply_constant_int64_int32_neon_LBB2_5:
+ MOVWU R8, R8 // mov w8, w8
+multiply_constant_int64_int32_neon_LBB2_6:
+ MOVD (R0)(R8<<3), R9 // ldr x9, [x0, x8, lsl #3]
+ MULW R3, R9, R9 // mul w9, w9, w3
+ MOVW R9, (R1)(R8<<2) // str w9, [x1, x8, lsl #2]
+ ADD $1, R8, R8 // add x8, x8, #1
+ CMPW R8, R2 // cmp w2, w8
+ BGT multiply_constant_int64_int32_neon_LBB2_6
+multiply_constant_int64_int32_neon_LBB2_7:
+ RET
+
+
+TEXT ·_multiply_constant_int64_int32_neon(SB), 4, $0-32
+
+ MOVD src+0(FP), R0
+ MOVD dest+8(FP), R1
+ MOVD len+16(FP), R2
+ MOVD factor+24(FP), R3
+ CALL ·_c2goasm_native_multiply_constant_int64_int32_neon(SB)
+ RET
+
+
+TEXT ·_c2goasm_native_multiply_constant_int64_int64_neon(SB), 516, $0-32
+
+ CMPW $2, R2 // cmp w2, #2
+ BGE multiply_constant_int64_int64_neon_LBB3_2
+ MOVW $0, R8 // mov w8, #0
+ CMPW R2, R8 // cmp w8, w2
+ BLT multiply_constant_int64_int64_neon_LBB3_5
+ B multiply_constant_int64_int64_neon_LBB3_7
+multiply_constant_int64_int64_neon_LBB3_2:
+ WORD $0x1e270060 // fmov s0, w3
+ LSR $32, R3, R8 // lsr x8, x3, #32
+ WORD $0x1e270101 // fmov s1, w8
+ MOVWU R2, R8 // mov w8, w2
+ MOVW $2, R9 // mov w9, #2
+ MOVD R0, R10 // mov x10, x0
+ MOVD R1, R11 // mov x11, x1
+multiply_constant_int64_int64_neon_LBB3_3:
+ WORD $0x3cc10542 // ldr q2, [x10], #16
+ WORD $0x0ea12843 // xtn.2s v3, v2
+ VEXT $8, V2.B16, V2.B16, V4.B16 // ext.16b v4, v2, v2, #8
+ VZIP2 V4.S2, V2.S2, V2.S2 // zip2.2s v2, v2, v4
+ WORD $0x2f80a042 // umull.2d v2, v2, v0[0]
+ WORD $0x2f812062 // umlal.2d v2, v3, v1[0]
+ VSHL $32, V2.D2, V2.D2 // shl.2d v2, v2, #32
+ WORD $0x2f802062 // umlal.2d v2, v3, v0[0]
+ WORD $0x3c810562 // str q2, [x11], #16
+ ADD $2, R9, R9 // add x9, x9, #2
+ CMP R8, R9 // cmp x9, x8
+ BLS multiply_constant_int64_int64_neon_LBB3_3
+ ANDW $2147483646, R2, R8 // and w8, w2, #0x7ffffffe
+ CMPW R2, R8 // cmp w8, w2
+ BGE multiply_constant_int64_int64_neon_LBB3_7
+multiply_constant_int64_int64_neon_LBB3_5:
+ MOVWU R8, R8 // mov w8, w8
+multiply_constant_int64_int64_neon_LBB3_6:
+ MOVD (R0)(R8<<3), R9 // ldr x9, [x0, x8, lsl #3]
+ MUL R3, R9, R9 // mul x9, x9, x3
+ MOVD R9, (R1)(R8<<3) // str x9, [x1, x8, lsl #3]
+ ADD $1, R8, R8 // add x8, x8, #1
+ CMPW R8, R2 // cmp w2, w8
+ BGT multiply_constant_int64_int64_neon_LBB3_6
+multiply_constant_int64_int64_neon_LBB3_7:
+ RET
+
+
+TEXT ·_multiply_constant_int64_int64_neon(SB), 4, $0-32
+
+ MOVD src+0(FP), R0
+ MOVD dest+8(FP), R1
+ MOVD len+16(FP), R2
+ MOVD factor+24(FP), R3
+ CALL ·_c2goasm_native_multiply_constant_int64_int64_neon(SB)
+ RET
diff --git
a/arrow/compute/internal/kernels/constant_factor_neon_arm64_native.go
b/arrow/compute/internal/kernels/constant_factor_neon_arm64_native.go
new file mode 100644
index 00000000..4da2f7d7
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_neon_arm64_native.go
@@ -0,0 +1,33 @@
+// 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"
+
+//go:noescape
+func _c2goasm_native_multiply_constant_int32_int32_neon(src, dest
unsafe.Pointer, len int, factor int64)
+
+//go:noescape
+func _c2goasm_native_multiply_constant_int32_int64_neon(src, dest
unsafe.Pointer, len int, factor int64)
+
+//go:noescape
+func _c2goasm_native_multiply_constant_int64_int32_neon(src, dest
unsafe.Pointer, len int, factor int64)
+
+//go:noescape
+func _c2goasm_native_multiply_constant_int64_int64_neon(src, dest
unsafe.Pointer, len int, factor int64)
diff --git a/arrow/compute/internal/kernels/constant_factor_neon_arm64_test.go
b/arrow/compute/internal/kernels/constant_factor_neon_arm64_test.go
new file mode 100644
index 00000000..cf208af7
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_neon_arm64_test.go
@@ -0,0 +1,43 @@
+// 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 (
+ "math"
+ "testing"
+)
+
+func TestNeonLengthFitsAssembly(t *testing.T) {
+ tests := []struct {
+ name string
+ length int
+ want bool
+ }{
+ {name: "maximum assembly length", length: math.MaxInt32, want:
true},
+ {name: "above maximum assembly length", length: math.MaxInt32 +
1, want: false},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := neonLengthFitsAssembly(tt.length); got !=
tt.want {
+ t.Fatalf("neonLengthFitsAssembly(%d) = %t, want
%t", tt.length, got, tt.want)
+ }
+ })
+ }
+}
diff --git a/arrow/compute/internal/kernels/constant_factor_test.go
b/arrow/compute/internal/kernels/constant_factor_test.go
new file mode 100644
index 00000000..da290fd7
--- /dev/null
+++ b/arrow/compute/internal/kernels/constant_factor_test.go
@@ -0,0 +1,241 @@
+// 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 kernels
+
+import (
+ "fmt"
+ "math/rand"
+ "reflect"
+ "runtime"
+ "strconv"
+ "testing"
+)
+
+const (
+ maxInt32Value = int32(1<<31 - 1)
+ minInt32Value = int32(-1 << 31)
+ maxInt64Value = int64(1<<63 - 1)
+ minInt64Value = int64(-1 << 63)
+)
+
+func TestMultiplyConstant(t *testing.T) {
+ lengths := []int{1, 2, 3, 4, 5, 7, 8, 15, 16, 31, 32, 33, 63, 64, 65}
+ factors := []int64{
+ 0,
+ 1,
+ -1,
+ 3,
+ -3,
+ 1_000_000,
+ 1 << 31,
+ -(1 << 31),
+ 1 << 40,
+ -(1 << 40),
+ maxInt64Value,
+ minInt64Value,
+ }
+
+ for _, length := range lengths {
+ input32 := makeInt32Values(length)
+ input64 := makeInt64Values(length)
+ for _, factor := range factors {
+ name := fmt.Sprintf("length_%d/factor_%s", length,
strconv.FormatInt(factor, 10))
+ t.Run(name, func(t *testing.T) {
+ assertMultiplyInt32Int32(t, input32, factor)
+ assertMultiplyInt32Int64(t, input32, factor)
+ assertMultiplyInt64Int32(t, input64, factor)
+ assertMultiplyInt64Int64(t, input64, factor)
+ })
+ }
+ }
+}
+
+func TestMultiplyConstantRandomized(t *testing.T) {
+ rng := rand.New(rand.NewSource(23))
+ for iteration := 0; iteration < 64; iteration++ {
+ for _, length := range []int{1, 2, 3, 4, 5, 7, 8, 9, 15, 16,
17, 65, 257} {
+ input32, input64 := make([]int32, length),
make([]int64, length)
+ for i := range input32 {
+ input32[i] = int32(rng.Uint32())
+ input64[i] = int64(rng.Uint64())
+ }
+ factor := int64(rng.Uint64())
+ assertMultiplyInt32Int32(t, input32, factor)
+ assertMultiplyInt32Int64(t, input32, factor)
+ assertMultiplyInt64Int32(t, input64, factor)
+ assertMultiplyInt64Int64(t, input64, factor)
+ }
+ }
+}
+
+func assertMultiplyInt32Int32(t *testing.T, input []int32, factor int64) {
+ t.Helper()
+ want := make([]int32, len(input))
+ multiplyConstantGo(input, want, factor)
+ got := make([]int32, len(input))
+ multiplyConstant(input, got, factor)
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("int32 -> int32 mismatch for factor %d: got %v, want
%v", factor, got, want)
+ }
+}
+
+func assertMultiplyInt32Int64(t *testing.T, input []int32, factor int64) {
+ t.Helper()
+ want := make([]int64, len(input))
+ multiplyConstantGo(input, want, factor)
+ got := make([]int64, len(input))
+ multiplyConstant(input, got, factor)
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("int32 -> int64 mismatch for factor %d: got %v, want
%v", factor, got, want)
+ }
+}
+
+func assertMultiplyInt64Int32(t *testing.T, input []int64, factor int64) {
+ t.Helper()
+ want := make([]int32, len(input))
+ multiplyConstantGo(input, want, factor)
+ got := make([]int32, len(input))
+ multiplyConstant(input, got, factor)
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("int64 -> int32 mismatch for factor %d: got %v, want
%v", factor, got, want)
+ }
+}
+
+func assertMultiplyInt64Int64(t *testing.T, input []int64, factor int64) {
+ t.Helper()
+ want := make([]int64, len(input))
+ multiplyConstantGo(input, want, factor)
+ got := make([]int64, len(input))
+ multiplyConstant(input, got, factor)
+ if !reflect.DeepEqual(got, want) {
+ t.Fatalf("int64 -> int64 mismatch for factor %d: got %v, want
%v", factor, got, want)
+ }
+}
+
+func makeInt32Values(length int) []int32 {
+ values := []int32{
+ 0,
+ 1,
+ -1,
+ 2,
+ -2,
+ 123456789,
+ -123456789,
+ maxInt32Value,
+ minInt32Value,
+ }
+ result := make([]int32, length)
+ for i := range result {
+ result[i] = values[i%len(values)]
+ }
+ return result
+}
+
+func makeInt64Values(length int) []int64 {
+ values := []int64{
+ 0,
+ 1,
+ -1,
+ 2,
+ -2,
+ 1 << 40,
+ -(1 << 40),
+ maxInt64Value,
+ minInt64Value,
+ }
+ result := make([]int64, length)
+ for i := range result {
+ result[i] = values[i%len(values)]
+ }
+ return result
+}
+
+func BenchmarkMultiplyConstant(b *testing.B) {
+ for _, size := range []int{1 << 10, 1 << 14, 1 << 20} {
+ b.Run(fmt.Sprintf("int32_int32/%d/generic", size), func(b
*testing.B) {
+ input := makeInt32Values(size)
+ output := make([]int32, size)
+ benchmarkMultiplyConstant(b, int64(size*4), output,
func() {
+ multiplyConstantGo(input, output, 1_000_000)
+ })
+ })
+ b.Run(fmt.Sprintf("int32_int32/%d/dispatch", size), func(b
*testing.B) {
+ input := makeInt32Values(size)
+ output := make([]int32, size)
+ benchmarkMultiplyConstant(b, int64(size*4), output,
func() {
+ multiplyConstant(input, output, 1_000_000)
+ })
+ })
+
+ b.Run(fmt.Sprintf("int32_int64/%d/generic", size), func(b
*testing.B) {
+ input := makeInt32Values(size)
+ output := make([]int64, size)
+ benchmarkMultiplyConstant(b, int64(size*4), output,
func() {
+ multiplyConstantGo(input, output, 1_000_000)
+ })
+ })
+ b.Run(fmt.Sprintf("int32_int64/%d/dispatch", size), func(b
*testing.B) {
+ input := makeInt32Values(size)
+ output := make([]int64, size)
+ benchmarkMultiplyConstant(b, int64(size*4), output,
func() {
+ multiplyConstant(input, output, 1_000_000)
+ })
+ })
+
+ b.Run(fmt.Sprintf("int64_int32/%d/generic", size), func(b
*testing.B) {
+ input := makeInt64Values(size)
+ output := make([]int32, size)
+ benchmarkMultiplyConstant(b, int64(size*8), output,
func() {
+ multiplyConstantGo(input, output, 1_000_000)
+ })
+ })
+ b.Run(fmt.Sprintf("int64_int32/%d/dispatch", size), func(b
*testing.B) {
+ input := makeInt64Values(size)
+ output := make([]int32, size)
+ benchmarkMultiplyConstant(b, int64(size*8), output,
func() {
+ multiplyConstant(input, output, 1_000_000)
+ })
+ })
+
+ b.Run(fmt.Sprintf("int64_int64/%d/generic", size), func(b
*testing.B) {
+ input := makeInt64Values(size)
+ output := make([]int64, size)
+ benchmarkMultiplyConstant(b, int64(size*8), output,
func() {
+ multiplyConstantGo(input, output, 1_000_000)
+ })
+ })
+ b.Run(fmt.Sprintf("int64_int64/%d/dispatch", size), func(b
*testing.B) {
+ input := makeInt64Values(size)
+ output := make([]int64, size)
+ benchmarkMultiplyConstant(b, int64(size*8), output,
func() {
+ multiplyConstant(input, output, 1_000_000)
+ })
+ })
+ }
+}
+
+func benchmarkMultiplyConstant(b *testing.B, bytes int64, output any, fn
func()) {
+ b.Helper()
+ b.ReportAllocs()
+ b.SetBytes(bytes)
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ fn()
+ }
+ b.StopTimer()
+ runtime.KeepAlive(output)
+}