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 e5218342 perf(internal/utils): add ARM64 NEON min/max for 8- and 
16-bit integers (#1281)
e5218342 is described below

commit e52183426beee2e2d5c8c507a1e4dce6fefe7e57
Author: Minh Vu <[email protected]>
AuthorDate: Thu Sep 3 18:49:57 2026 +0200

    perf(internal/utils): add ARM64 NEON min/max for 8- and 16-bit integers 
(#1281)
    
    ## Summary
    
    - Add ARM64 NEON min/max kernels for int8, uint8, int16, and uint16.
    - Keep scalar tails and scalar fallback when ASIMD is unavailable or
    `noasm` is used.
    - Add boundary, extreme-value, and benchmark coverage.
    
    ## Benchmark
    
    Apple M1 Pro, n=65536, median of 3 runs:
    
    | Type | noasm | NEON |
    | --- | ---: | ---: |
    | int8 | 65,642 ns/op | 1,433 ns/op |
    | uint8 | 66,233 ns/op | 1,394 ns/op |
    | int16 | 65,431 ns/op | 2,807 ns/op |
    | uint16 | 66,756 ns/op | 2,726 ns/op |
    
    ## Checks
    
    - `go test ./internal/utils -count=1`
    - `go test -race ./internal/utils -count=1`
    - `go test -tags noasm ./internal/utils -count=1`
    - `ARM_ENABLE_EXT=bogus go test ./internal/utils -count=1`
    - `go vet ./internal/utils`
    - `GOARCH=amd64 go test -c -o /dev/null ./internal/utils`
---
 internal/utils/min_max.go            |  40 ++---
 internal/utils/min_max_arm64.go      |  14 +-
 internal/utils/min_max_neon_arm64.go |  32 ++++
 internal/utils/min_max_neon_arm64.s  | 278 +++++++++++++++++++++++++++++++++++
 internal/utils/min_max_test.go       | 206 ++++++++++++++++++++++++++
 5 files changed, 540 insertions(+), 30 deletions(-)

diff --git a/internal/utils/min_max.go b/internal/utils/min_max.go
index 3d7b0024..019ee5ab 100644
--- a/internal/utils/min_max.go
+++ b/internal/utils/min_max.go
@@ -155,58 +155,50 @@ var minmaxFuncs = struct {
        ui64 func([]uint64) (uint64, uint64)
 }{}
 
-// GetMinMaxInt8 returns the min and max for a int8 slice, using AVX2 or
-// SSE4 cpu extensions if available, falling back to a pure go implementation
-// if they are unavailable or built with the noasm tag.
+// GetMinMaxInt8 returns the min and max for an int8 slice, using a SIMD
+// implementation if available and falling back to pure Go otherwise.
 func GetMinMaxInt8(v []int8) (min, max int8) {
        return minmaxFuncs.i8(v)
 }
 
-// GetMinMaxUint8 returns the min and max for a uint8 slice, using AVX2 or
-// SSE4 cpu extensions if available, falling back to a pure go implementation
-// if they are unavailable or built with the noasm tag.
+// GetMinMaxUint8 returns the min and max for a uint8 slice, using a SIMD
+// implementation if available and falling back to pure Go otherwise.
 func GetMinMaxUint8(v []uint8) (min, max uint8) {
        return minmaxFuncs.ui8(v)
 }
 
-// GetMinMaxInt16 returns the min and max for a int16 slice, using AVX2 or
-// SSE4 cpu extensions if available, falling back to a pure go implementation
-// if they are unavailable or built with the noasm tag.
+// GetMinMaxInt16 returns the min and max for an int16 slice, using a SIMD
+// implementation if available and falling back to pure Go otherwise.
 func GetMinMaxInt16(v []int16) (min, max int16) {
        return minmaxFuncs.i16(v)
 }
 
-// GetMinMaxUint16 returns the min and max for a uint16 slice, using AVX2 or
-// SSE4 cpu extensions if available, falling back to a pure go implementation
-// if they are unavailable or built with the noasm tag.
+// GetMinMaxUint16 returns the min and max for a uint16 slice, using a SIMD
+// implementation if available and falling back to pure Go otherwise.
 func GetMinMaxUint16(v []uint16) (min, max uint16) {
        return minmaxFuncs.ui16(v)
 }
 
-// GetMinMaxInt32 returns the min and max for a int32 slice, using AVX2 or
-// SSE4 cpu extensions if available, falling back to a pure go implementation
-// if they are unavailable or built with the noasm tag.
+// GetMinMaxInt32 returns the min and max for an int32 slice, using a SIMD
+// implementation if available and falling back to pure Go otherwise.
 func GetMinMaxInt32(v []int32) (min, max int32) {
        return minmaxFuncs.i32(v)
 }
 
-// GetMinMaxUint32 returns the min and max for a uint32 slice, using AVX2 or
-// SSE4 cpu extensions if available, falling back to a pure go implementation
-// if they are unavailable or built with the noasm tag.
+// GetMinMaxUint32 returns the min and max for a uint32 slice, using a SIMD
+// implementation if available and falling back to pure Go otherwise.
 func GetMinMaxUint32(v []uint32) (min, max uint32) {
        return minmaxFuncs.ui32(v)
 }
 
-// GetMinMaxInt64 returns the min and max for a int64 slice, using AVX2 or
-// SSE4 cpu extensions if available, falling back to a pure go implementation
-// if they are unavailable or built with the noasm tag.
+// GetMinMaxInt64 returns the min and max for an int64 slice, using a SIMD
+// implementation if available and falling back to pure Go otherwise.
 func GetMinMaxInt64(v []int64) (min, max int64) {
        return minmaxFuncs.i64(v)
 }
 
-// GetMinMaxUint64 returns the min and max for a uint64 slice, using AVX2 or
-// SSE4 cpu extensions if available, falling back to a pure go implementation
-// if they are unavailable or built with the noasm tag.
+// GetMinMaxUint64 returns the min and max for a uint64 slice, using a SIMD
+// implementation if available and falling back to pure Go otherwise.
 func GetMinMaxUint64(v []uint64) (min, max uint64) {
        return minmaxFuncs.ui64(v)
 }
diff --git a/internal/utils/min_max_arm64.go b/internal/utils/min_max_arm64.go
index d0284966..c8ae0ea8 100644
--- a/internal/utils/min_max_arm64.go
+++ b/internal/utils/min_max_arm64.go
@@ -47,20 +47,22 @@ func init() {
                }
        }
        if cpu.ARM64.HasASIMD {
+               minmaxFuncs.i8 = int8MaxMinNEON
+               minmaxFuncs.ui8 = uint8MaxMinNEON
+               minmaxFuncs.i16 = int16MaxMinNEON
+               minmaxFuncs.ui16 = uint16MaxMinNEON
                minmaxFuncs.i32 = int32MaxMinNEON
                minmaxFuncs.ui32 = uint32MaxMinNEON
                minmaxFuncs.i64 = int64MaxMinNEON
                minmaxFuncs.ui64 = uint64MaxMinNEON
        } else {
+               minmaxFuncs.i8 = int8MinMax
+               minmaxFuncs.ui8 = uint8MinMax
+               minmaxFuncs.i16 = int16MinMax
+               minmaxFuncs.ui16 = uint16MinMax
                minmaxFuncs.i32 = int32MinMax
                minmaxFuncs.ui32 = uint32MinMax
                minmaxFuncs.i64 = int64MinMax
                minmaxFuncs.ui64 = uint64MinMax
        }
-
-       // haven't yet generated the NEON arm64 for these
-       minmaxFuncs.i8 = int8MinMax
-       minmaxFuncs.ui8 = uint8MinMax
-       minmaxFuncs.i16 = int16MinMax
-       minmaxFuncs.ui16 = uint16MinMax
 }
diff --git a/internal/utils/min_max_neon_arm64.go 
b/internal/utils/min_max_neon_arm64.go
index 043201ad..7dc1ee7b 100644
--- a/internal/utils/min_max_neon_arm64.go
+++ b/internal/utils/min_max_neon_arm64.go
@@ -23,6 +23,38 @@ import "unsafe"
 // This file contains convenience functions for utilizing Arm64 Neon 
intrinsics to quickly
 // and efficiently get the min and max from an integral slice.
 
+//go:noescape
+func _int8_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
+
+func int8MaxMinNEON(values []int8) (min, max int8) {
+       _int8_max_min_neon(unsafe.Pointer(unsafe.SliceData(values)), 
len(values), unsafe.Pointer(&min), unsafe.Pointer(&max))
+       return
+}
+
+//go:noescape
+func _uint8_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
+
+func uint8MaxMinNEON(values []uint8) (min, max uint8) {
+       _uint8_max_min_neon(unsafe.Pointer(unsafe.SliceData(values)), 
len(values), unsafe.Pointer(&min), unsafe.Pointer(&max))
+       return
+}
+
+//go:noescape
+func _int16_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
+
+func int16MaxMinNEON(values []int16) (min, max int16) {
+       _int16_max_min_neon(unsafe.Pointer(unsafe.SliceData(values)), 
len(values), unsafe.Pointer(&min), unsafe.Pointer(&max))
+       return
+}
+
+//go:noescape
+func _uint16_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
+
+func uint16MaxMinNEON(values []uint16) (min, max uint16) {
+       _uint16_max_min_neon(unsafe.Pointer(unsafe.SliceData(values)), 
len(values), unsafe.Pointer(&min), unsafe.Pointer(&max))
+       return
+}
+
 //go:noescape
 func _int32_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
 
diff --git a/internal/utils/min_max_neon_arm64.s 
b/internal/utils/min_max_neon_arm64.s
index a037057e..24a28feb 100644
--- a/internal/utils/min_max_neon_arm64.s
+++ b/internal/utils/min_max_neon_arm64.s
@@ -1,9 +1,287 @@
 //+build !noasm !appengine
 
 // ARROW-15336: optimized NEON min/max for ARM64
+// 8-bit functions use .16b (128-bit Q registers, 16 lanes) processing 32 
elements/iteration
+// 16-bit functions use .8h (128-bit Q registers, 8 lanes) processing 16 
elements/iteration
 // 32-bit functions use .4s (128-bit Q registers, 4 lanes) processing 8 
elements/iteration
 // 64-bit functions use BIT/BIF instead of BSL+MOV to eliminate register saves
 
+// func _int8_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
+TEXT ·_int8_max_min_neon(SB), $0-32
+
+       MOVD    values+0(FP), R0
+       MOVD    length+8(FP), R1
+       MOVD    minout+16(FP), R2
+       MOVD    maxout+24(FP), R3
+
+       WORD $0x7100043f // cmp    w1, #1
+       BLT int8_early_exit
+
+       WORD $0x71007c3f // cmp    w1, #31
+       WORD $0x2a0103e8 // mov    w8, w1
+       BHI int8_neon
+
+       WORD $0xaa1f03e9 // mov    x9, xzr
+       WORD $0x5280100b // mov    w11, #128
+       WORD $0x52800fea // mov    w10, #127
+       JMP int8_scalar
+int8_early_exit:
+       WORD $0x52800fea // mov    w10, #127
+       WORD $0x5280100b // mov    w11, #128
+       WORD $0x3900006b // strb   w11, [x3]
+       WORD $0x3900004a // strb   w10, [x2]
+       RET
+int8_neon:
+       WORD $0x927be909 // and    x9, x8, #0xffffffffffffffe0
+       WORD $0x4f03e7e0 // movi   v0.16b, #127
+       WORD $0x4f03e7e1 // movi   v1.16b, #127
+       WORD $0x4f04e402 // movi   v2.16b, #128
+       WORD $0x4f04e403 // movi   v3.16b, #128
+       WORD $0x9100400a // add    x10, x0, #16
+       WORD $0xaa0903eb // mov    x11, x9
+int8_loop:
+       WORD $0xad7f9544 // ldp    q4, q5, [x10, #-16]
+       WORD $0x4e246c00 // smin   v0.16b, v0.16b, v4.16b
+       WORD $0x4e256c21 // smin   v1.16b, v1.16b, v5.16b
+       WORD $0x4e246442 // smax   v2.16b, v2.16b, v4.16b
+       WORD $0x4e256463 // smax   v3.16b, v3.16b, v5.16b
+       WORD $0xf100816b // subs   x11, x11, #32
+       WORD $0x9100814a // add    x10, x10, #32
+       BNE int8_loop
+
+       WORD $0x4e216c00 // smin   v0.16b, v0.16b, v1.16b
+       WORD $0x4e236442 // smax   v2.16b, v2.16b, v3.16b
+       WORD $0x4e31a800 // sminv  b0, v0.16b
+       WORD $0x4e30a841 // smaxv  b1, v2.16b
+       WORD $0xeb08013f // cmp    x9, x8
+       WORD $0x1e26000a // fmov   w10, s0
+       WORD $0x1e26002b // fmov   w11, s1
+       BEQ int8_done
+int8_scalar:
+       WORD $0x8b09000c // add    x12, x0, x9
+       WORD $0xcb090108 // sub    x8, x8, x9
+int8_scalar_loop:
+       WORD $0x38c01589 // ldrsb  w9, [x12], #1
+       WORD $0x13001d4a // sxtb   w10, w10
+       WORD $0x6b09015f // cmp    w10, w9
+       WORD $0x1a89b14a // csel   w10, w10, w9, lt
+       WORD $0x13001d6b // sxtb   w11, w11
+       WORD $0x6b09017f // cmp    w11, w9
+       WORD $0x1a89c16b // csel   w11, w11, w9, gt
+       WORD $0xf1000508 // subs   x8, x8, #1
+       BNE int8_scalar_loop
+int8_done:
+       WORD $0x3900006b // strb   w11, [x3]
+       WORD $0x3900004a // strb   w10, [x2]
+       RET
+
+// func _uint8_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
+TEXT ·_uint8_max_min_neon(SB), $0-32
+
+       MOVD    values+0(FP), R0
+       MOVD    length+8(FP), R1
+       MOVD    minout+16(FP), R2
+       MOVD    maxout+24(FP), R3
+
+       WORD $0x7100043f // cmp    w1, #1
+       BLT uint8_early_exit
+
+       WORD $0x71007c3f // cmp    w1, #31
+       WORD $0x2a0103e8 // mov    w8, w1
+       BHI uint8_neon
+
+       WORD $0xaa1f03e9 // mov    x9, xzr
+       WORD $0x5280000b // mov    w11, #0
+       WORD $0x52801fea // mov    w10, #255
+       JMP uint8_scalar
+uint8_early_exit:
+       WORD $0x5280000b // mov    w11, #0
+       WORD $0x52801fea // mov    w10, #255
+       WORD $0x3900006b // strb   w11, [x3]
+       WORD $0x3900004a // strb   w10, [x2]
+       RET
+uint8_neon:
+       WORD $0x927be909 // and    x9, x8, #0xffffffffffffffe0
+       WORD $0x6f07e7e0 // mvni   v0.16b, #0
+       WORD $0x6f07e7e1 // mvni   v1.16b, #0
+       WORD $0x6f00e402 // movi   v2.16b, #0
+       WORD $0x6f00e403 // movi   v3.16b, #0
+       WORD $0x9100400a // add    x10, x0, #16
+       WORD $0xaa0903eb // mov    x11, x9
+uint8_loop:
+       WORD $0xad7f9544 // ldp    q4, q5, [x10, #-16]
+       WORD $0x6e246c00 // umin   v0.16b, v0.16b, v4.16b
+       WORD $0x6e256c21 // umin   v1.16b, v1.16b, v5.16b
+       WORD $0x6e246442 // umax   v2.16b, v2.16b, v4.16b
+       WORD $0x6e256463 // umax   v3.16b, v3.16b, v5.16b
+       WORD $0xf100816b // subs   x11, x11, #32
+       WORD $0x9100814a // add    x10, x10, #32
+       BNE uint8_loop
+
+       WORD $0x6e216c00 // umin   v0.16b, v0.16b, v1.16b
+       WORD $0x6e236442 // umax   v2.16b, v2.16b, v3.16b
+       WORD $0x6e31a800 // uminv  b0, v0.16b
+       WORD $0x6e30a841 // umaxv  b1, v2.16b
+       WORD $0xeb08013f // cmp    x9, x8
+       WORD $0x1e26000a // fmov   w10, s0
+       WORD $0x1e26002b // fmov   w11, s1
+       BEQ uint8_done
+uint8_scalar:
+       WORD $0x8b09000c // add    x12, x0, x9
+       WORD $0xcb090108 // sub    x8, x8, x9
+uint8_scalar_loop:
+       WORD $0x38401589 // ldrb   w9, [x12], #1
+       WORD $0x12001d4a // and    w10, w10, #0xff
+       WORD $0x6b09015f // cmp    w10, w9
+       WORD $0x1a89314a // csel   w10, w10, w9, lo
+       WORD $0x12001d6b // and    w11, w11, #0xff
+       WORD $0x6b09017f // cmp    w11, w9
+       WORD $0x1a89816b // csel   w11, w11, w9, hi
+       WORD $0xf1000508 // subs   x8, x8, #1
+       BNE uint8_scalar_loop
+uint8_done:
+       WORD $0x3900006b // strb   w11, [x3]
+       WORD $0x3900004a // strb   w10, [x2]
+       RET
+
+// func _int16_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
+TEXT ·_int16_max_min_neon(SB), $0-32
+
+       MOVD    values+0(FP), R0
+       MOVD    length+8(FP), R1
+       MOVD    minout+16(FP), R2
+       MOVD    maxout+24(FP), R3
+
+       WORD $0x7100043f // cmp    w1, #1
+       BLT int16_early_exit
+
+       WORD $0x71003c3f // cmp    w1, #15
+       WORD $0x2a0103e8 // mov    w8, w1
+       BHI int16_neon
+
+       WORD $0xaa1f03e9 // mov    x9, xzr
+       WORD $0x5290000b // mov    w11, #32768
+       WORD $0x528fffea // mov    w10, #32767
+       JMP int16_scalar
+int16_early_exit:
+       WORD $0x528fffea // mov    w10, #32767
+       WORD $0x5290000b // mov    w11, #32768
+       WORD $0x7900006b // strh   w11, [x3]
+       WORD $0x7900004a // strh   w10, [x2]
+       RET
+int16_neon:
+       WORD $0x927ced09 // and    x9, x8, #0xfffffffffffffff0
+       WORD $0x6f04a400 // mvni   v0.8h, #32768
+       WORD $0x6f04a401 // mvni   v1.8h, #32768
+       WORD $0x4f04a402 // movi   v2.8h, #32768
+       WORD $0x4f04a403 // movi   v3.8h, #32768
+       WORD $0x9100400a // add    x10, x0, #16
+       WORD $0xaa0903eb // mov    x11, x9
+int16_loop:
+       WORD $0xad7f9544 // ldp    q4, q5, [x10, #-16]
+       WORD $0x4e646c00 // smin   v0.8h, v0.8h, v4.8h
+       WORD $0x4e656c21 // smin   v1.8h, v1.8h, v5.8h
+       WORD $0x4e646442 // smax   v2.8h, v2.8h, v4.8h
+       WORD $0x4e656463 // smax   v3.8h, v3.8h, v5.8h
+       WORD $0xf100416b // subs   x11, x11, #16
+       WORD $0x9100814a // add    x10, x10, #32
+       BNE int16_loop
+
+       WORD $0x4e616c00 // smin   v0.8h, v0.8h, v1.8h
+       WORD $0x4e636442 // smax   v2.8h, v2.8h, v3.8h
+       WORD $0x4e71a800 // sminv  h0, v0.8h
+       WORD $0x4e70a841 // smaxv  h1, v2.8h
+       WORD $0xeb08013f // cmp    x9, x8
+       WORD $0x1e26000a // fmov   w10, s0
+       WORD $0x1e26002b // fmov   w11, s1
+       BEQ int16_done
+int16_scalar:
+       WORD $0x8b09040c // add    x12, x0, x9, lsl #1
+       WORD $0xcb090108 // sub    x8, x8, x9
+int16_scalar_loop:
+       WORD $0x78c02589 // ldrsh  w9, [x12], #2
+       WORD $0x13003d4a // sxth   w10, w10
+       WORD $0x6b09015f // cmp    w10, w9
+       WORD $0x1a89b14a // csel   w10, w10, w9, lt
+       WORD $0x13003d6b // sxth   w11, w11
+       WORD $0x6b09017f // cmp    w11, w9
+       WORD $0x1a89c16b // csel   w11, w11, w9, gt
+       WORD $0xf1000508 // subs   x8, x8, #1
+       BNE int16_scalar_loop
+int16_done:
+       WORD $0x7900006b // strh   w11, [x3]
+       WORD $0x7900004a // strh   w10, [x2]
+       RET
+
+// func _uint16_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
+TEXT ·_uint16_max_min_neon(SB), $0-32
+
+       MOVD    values+0(FP), R0
+       MOVD    length+8(FP), R1
+       MOVD    minout+16(FP), R2
+       MOVD    maxout+24(FP), R3
+
+       WORD $0x7100043f // cmp    w1, #1
+       BLT uint16_early_exit
+
+       WORD $0x71003c3f // cmp    w1, #15
+       WORD $0x2a0103e8 // mov    w8, w1
+       BHI uint16_neon
+
+       WORD $0xaa1f03e9 // mov    x9, xzr
+       WORD $0x5280000b // mov    w11, #0
+       WORD $0x529fffea // mov    w10, #65535
+       JMP uint16_scalar
+uint16_early_exit:
+       WORD $0x5280000b // mov    w11, #0
+       WORD $0x529fffea // mov    w10, #65535
+       WORD $0x7900006b // strh   w11, [x3]
+       WORD $0x7900004a // strh   w10, [x2]
+       RET
+uint16_neon:
+       WORD $0x927ced09 // and    x9, x8, #0xfffffffffffffff0
+       WORD $0x6f07e7e0 // mvni   v0.8h, #0
+       WORD $0x6f07e7e1 // mvni   v1.8h, #0
+       WORD $0x6f00e402 // movi   v2.8h, #0
+       WORD $0x6f00e403 // movi   v3.8h, #0
+       WORD $0x9100400a // add    x10, x0, #16
+       WORD $0xaa0903eb // mov    x11, x9
+uint16_loop:
+       WORD $0xad7f9544 // ldp    q4, q5, [x10, #-16]
+       WORD $0x6e646c00 // umin   v0.8h, v0.8h, v4.8h
+       WORD $0x6e656c21 // umin   v1.8h, v1.8h, v5.8h
+       WORD $0x6e646442 // umax   v2.8h, v2.8h, v4.8h
+       WORD $0x6e656463 // umax   v3.8h, v3.8h, v5.8h
+       WORD $0xf100416b // subs   x11, x11, #16
+       WORD $0x9100814a // add    x10, x10, #32
+       BNE uint16_loop
+
+       WORD $0x6e616c00 // umin   v0.8h, v0.8h, v1.8h
+       WORD $0x6e636442 // umax   v2.8h, v2.8h, v3.8h
+       WORD $0x6e71a800 // uminv  h0, v0.8h
+       WORD $0x6e70a841 // umaxv  h1, v2.8h
+       WORD $0xeb08013f // cmp    x9, x8
+       WORD $0x1e26000a // fmov   w10, s0
+       WORD $0x1e26002b // fmov   w11, s1
+       BEQ uint16_done
+uint16_scalar:
+       WORD $0x8b09040c // add    x12, x0, x9, lsl #1
+       WORD $0xcb090108 // sub    x8, x8, x9
+uint16_scalar_loop:
+       WORD $0x78402589 // ldrh   w9, [x12], #2
+       WORD $0x12003d4a // and    w10, w10, #0xffff
+       WORD $0x6b09015f // cmp    w10, w9
+       WORD $0x1a89314a // csel   w10, w10, w9, lo
+       WORD $0x12003d6b // and    w11, w11, #0xffff
+       WORD $0x6b09017f // cmp    w11, w9
+       WORD $0x1a89816b // csel   w11, w11, w9, hi
+       WORD $0xf1000508 // subs   x8, x8, #1
+       BNE uint16_scalar_loop
+uint16_done:
+       WORD $0x7900006b // strh   w11, [x3]
+       WORD $0x7900004a // strh   w10, [x2]
+       RET
+
 // func _int32_max_min_neon(values unsafe.Pointer, length int, minout, maxout 
unsafe.Pointer)
 TEXT ·_int32_max_min_neon(SB), $0-32
 
diff --git a/internal/utils/min_max_test.go b/internal/utils/min_max_test.go
index 59da6910..452863de 100644
--- a/internal/utils/min_max_test.go
+++ b/internal/utils/min_max_test.go
@@ -45,6 +45,140 @@ func TestMinMaxInt32(t *testing.T) {
        }
 }
 
+func TestMinMaxInt8(t *testing.T) {
+       for _, size := range []int{0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 
33, 63, 64, 100, 1024} {
+               t.Run(fmt.Sprintf("n=%d", size), func(t *testing.T) {
+                       r := rand.New(&rand.PCG{}) // zero-seed for 
reproducibility
+                       values := make([]int8, size)
+                       for i := range values {
+                               values[i] = int8(r.Int32())
+                       }
+                       if size > 0 {
+                               values[r.IntN(size)] = math.MinInt8
+                               values[r.IntN(size)] = math.MaxInt8
+                       }
+
+                       goMin, goMax := int8MinMax(values)
+                       min, max := GetMinMaxInt8(values)
+                       if min != goMin || max != goMax {
+                               t.Errorf("n=%d: got min=%d max=%d, want min=%d 
max=%d", size, min, max, goMin, goMax)
+                       }
+               })
+       }
+}
+
+func TestMinMaxUint8(t *testing.T) {
+       for _, size := range []int{0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 
33, 63, 64, 100, 1024} {
+               t.Run(fmt.Sprintf("n=%d", size), func(t *testing.T) {
+                       values := make([]uint8, size)
+                       r := rand.New(&rand.PCG{}) // zero-seed for 
reproducibility
+                       for i := range values {
+                               values[i] = uint8(r.Uint32())
+                       }
+                       if size > 0 {
+                               values[r.IntN(size)] = 0
+                               values[r.IntN(size)] = math.MaxUint8
+                       }
+
+                       goMin, goMax := uint8MinMax(values)
+                       min, max := GetMinMaxUint8(values)
+                       if min != goMin || max != goMax {
+                               t.Errorf("n=%d: got min=%d max=%d, want min=%d 
max=%d", size, min, max, goMin, goMax)
+                       }
+               })
+       }
+}
+
+func TestMinMaxInt16(t *testing.T) {
+       for _, size := range []int{0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 
33, 63, 64, 100, 1024} {
+               t.Run(fmt.Sprintf("n=%d", size), func(t *testing.T) {
+                       r := rand.New(&rand.PCG{}) // zero-seed for 
reproducibility
+                       values := make([]int16, size)
+                       for i := range values {
+                               values[i] = int16(r.Int32())
+                       }
+                       if size > 0 {
+                               values[r.IntN(size)] = math.MinInt16
+                               values[r.IntN(size)] = math.MaxInt16
+                       }
+
+                       goMin, goMax := int16MinMax(values)
+                       min, max := GetMinMaxInt16(values)
+                       if min != goMin || max != goMax {
+                               t.Errorf("n=%d: got min=%d max=%d, want min=%d 
max=%d", size, min, max, goMin, goMax)
+                       }
+               })
+       }
+}
+
+func TestMinMaxUint16(t *testing.T) {
+       for _, size := range []int{0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 17, 31, 32, 
33, 63, 64, 100, 1024} {
+               t.Run(fmt.Sprintf("n=%d", size), func(t *testing.T) {
+                       values := make([]uint16, size)
+                       r := rand.New(&rand.PCG{}) // zero-seed for 
reproducibility
+                       for i := range values {
+                               values[i] = uint16(r.Uint32())
+                       }
+                       if size > 0 {
+                               values[r.IntN(size)] = 0
+                               values[r.IntN(size)] = math.MaxUint16
+                       }
+
+                       goMin, goMax := uint16MinMax(values)
+                       min, max := GetMinMaxUint16(values)
+                       if min != goMin || max != goMax {
+                               t.Errorf("n=%d: got min=%d max=%d, want min=%d 
max=%d", size, min, max, goMin, goMax)
+                       }
+               })
+       }
+}
+
+func TestMinMaxNarrowNoExtrema(t *testing.T) {
+       t.Run("int8", func(t *testing.T) {
+               values := make([]int8, 32)
+               for i := range values {
+                       values[i] = int8(i - 16)
+               }
+               min, max := GetMinMaxInt8(values)
+               if min != -16 || max != 15 {
+                       t.Errorf("got min=%d max=%d, want min=-16 max=15", min, 
max)
+               }
+       })
+
+       t.Run("uint8", func(t *testing.T) {
+               values := make([]uint8, 32)
+               for i := range values {
+                       values[i] = uint8(i + 1)
+               }
+               min, max := GetMinMaxUint8(values)
+               if min != 1 || max != 32 {
+                       t.Errorf("got min=%d max=%d, want min=1 max=32", min, 
max)
+               }
+       })
+
+       t.Run("int16", func(t *testing.T) {
+               values := make([]int16, 16)
+               for i := range values {
+                       values[i] = int16(i*3 - 24)
+               }
+               min, max := GetMinMaxInt16(values)
+               if min != -24 || max != 21 {
+                       t.Errorf("got min=%d max=%d, want min=-24 max=21", min, 
max)
+               }
+       })
+
+       t.Run("uint16", func(t *testing.T) {
+               values := make([]uint16, 16)
+               for i := range values {
+                       values[i] = uint16(i*5 + 1)
+               }
+               min, max := GetMinMaxUint16(values)
+               if min != 1 || max != 76 {
+                       t.Errorf("got min=%d max=%d, want min=1 max=76", min, 
max)
+               }
+       })
+}
+
 func TestMinMaxUint32(t *testing.T) {
        for _, size := range []int{0, 1, 2, 3, 4, 7, 8, 9, 15, 16, 31, 63, 64, 
100, 1024} {
                t.Run(fmt.Sprintf("n=%d", size), func(t *testing.T) {
@@ -112,6 +246,14 @@ func TestMinMaxUint64(t *testing.T) {
 }
 
 var (
+       benchMinI8  int8
+       benchMaxI8  int8
+       benchMinU8  uint8
+       benchMaxU8  uint8
+       benchMinI16 int16
+       benchMaxI16 int16
+       benchMinU16 uint16
+       benchMaxU16 uint16
        benchMinI32 int32
        benchMaxI32 int32
        benchMinU32 uint32
@@ -122,6 +264,70 @@ var (
        benchMaxU64 uint64
 )
 
+func BenchmarkMinMaxInt8(b *testing.B) {
+       for _, size := range []int{0, 1, 7, 8, 15, 16, 31, 32, 64, 256, 1024, 
8192, 65536} {
+               values := make([]int8, size)
+               r := rand.New(&rand.PCG{}) // zero-seed for reproducibility
+               for i := range values {
+                       values[i] = int8(r.Int32())
+               }
+               b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) {
+                       b.SetBytes(int64(size))
+                       for i := 0; i < b.N; i++ {
+                               benchMinI8, benchMaxI8 = GetMinMaxInt8(values)
+                       }
+               })
+       }
+}
+
+func BenchmarkMinMaxUint8(b *testing.B) {
+       for _, size := range []int{0, 1, 7, 8, 15, 16, 31, 32, 64, 256, 1024, 
8192, 65536} {
+               values := make([]uint8, size)
+               r := rand.New(&rand.PCG{}) // zero-seed for reproducibility
+               for i := range values {
+                       values[i] = uint8(r.Uint32())
+               }
+               b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) {
+                       b.SetBytes(int64(size))
+                       for i := 0; i < b.N; i++ {
+                               benchMinU8, benchMaxU8 = GetMinMaxUint8(values)
+                       }
+               })
+       }
+}
+
+func BenchmarkMinMaxInt16(b *testing.B) {
+       for _, size := range []int{0, 1, 7, 8, 15, 16, 31, 32, 64, 256, 1024, 
8192, 65536} {
+               values := make([]int16, size)
+               r := rand.New(&rand.PCG{}) // zero-seed for reproducibility
+               for i := range values {
+                       values[i] = int16(r.Int32())
+               }
+               b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) {
+                       b.SetBytes(int64(size) * 2)
+                       for i := 0; i < b.N; i++ {
+                               benchMinI16, benchMaxI16 = 
GetMinMaxInt16(values)
+                       }
+               })
+       }
+}
+
+func BenchmarkMinMaxUint16(b *testing.B) {
+       for _, size := range []int{0, 1, 7, 8, 15, 16, 31, 32, 64, 256, 1024, 
8192, 65536} {
+               values := make([]uint16, size)
+               r := rand.New(&rand.PCG{}) // zero-seed for reproducibility
+               for i := range values {
+                       values[i] = uint16(r.Uint32())
+               }
+               b.Run(fmt.Sprintf("n=%d", size), func(b *testing.B) {
+                       b.SetBytes(int64(size) * 2)
+                       for i := 0; i < b.N; i++ {
+                               benchMinU16, benchMaxU16 = 
GetMinMaxUint16(values)
+                       }
+               })
+       }
+}
+
 func BenchmarkMinMaxInt32(b *testing.B) {
        for _, size := range []int{64, 256, 1024, 8192, 65536} {
                values := make([]int32, size)

Reply via email to