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)