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 8ae60185 perf(compute): vectorize mixed 16-bit filters (#1349)
8ae60185 is described below
commit 8ae601850b60514ebf5846f94f3bb75924e350a5
Author: Minh Vu <[email protected]>
AuthorDate: Tue Oct 6 18:19:28 2026 +0200
perf(compute): vectorize mixed 16-bit filters (#1349)
## What
- Add AVX2 and ARM64 NEON paths for mixed null-free 16-bit filters.
## Why
- 16-bit primitive filters still use the generic writer while 32-bit and
64-bit filters have SIMD paths.
## Implementation
- Keep nulls, short or unaligned filters, dense masks, and noasm on the
existing path.
- Benchmark alternating, random25/50/75, clustered50, all-selected, and
none-selected at 1K, 64K, and 1M values.
- On M1 Pro, compared with noasm: 1M alternating improves from 4.23 ms
to 225 us; random50 improves from 3.52 ms to 1.09 ms. Mixed cases drop
from 33 to 32 allocs/op.
---
arrow/compute/internal/kernels/Makefile | 9 +-
.../compute/internal/kernels/_lib/filter_uint16.cc | 80 +++++++++++
.../kernels/_lib/filter_uint16_avx2_amd64.s | 92 ++++++++++++
arrow/compute/internal/kernels/filter_uint16.go | 69 +++++++++
.../internal/kernels/filter_uint16_arch_test.go | 155 +++++++++++++++++++++
.../internal/kernels/filter_uint16_avx2_amd64.go | 50 +++++++
.../internal/kernels/filter_uint16_avx2_amd64.s | 93 +++++++++++++
.../kernels/filter_uint16_avx2_amd64_test.go | 53 +++++++
.../internal/kernels/filter_uint16_avx2_noasm.go | 23 +++
.../internal/kernels/filter_uint16_neon_arm64.go | 50 +++++++
.../internal/kernels/filter_uint16_neon_arm64.s | 120 ++++++++++++++++
.../kernels/filter_uint16_neon_arm64_test.go | 53 +++++++
.../internal/kernels/filter_uint16_neon_noasm.go | 23 +++
arrow/compute/internal/kernels/vector_selection.go | 8 ++
.../internal/kernels/vector_selection_test.go | 56 ++++++++
arrow/compute/vector_selection_bench_test.go | 78 ++++++++++-
arrow/compute/vector_selection_test.go | 47 +++++++
17 files changed, 1055 insertions(+), 4 deletions(-)
diff --git a/arrow/compute/internal/kernels/Makefile
b/arrow/compute/internal/kernels/Makefile
index a1a8b419..d33a8a9a 100644
--- a/arrow/compute/internal/kernels/Makefile
+++ b/arrow/compute/internal/kernels/Makefile
@@ -42,7 +42,7 @@ INTEL_SOURCES := \
cast_numeric_avx2_amd64.s cast_numeric_sse4_amd64.s
constant_factor_avx2_amd64.s \
constant_factor_sse4_amd64.s base_arithmetic_avx2_amd64.s
base_arithmetic_sse4_amd64.s \
scalar_comparison_avx2_amd64.s scalar_comparison_sse4_amd64.s \
- filter_uint32_avx2_amd64.s filter_uint64_avx2_amd64.s
+ filter_uint32_avx2_amd64.s filter_uint64_avx2_amd64.s
filter_uint16_avx2_amd64.s
#
# ARROW-15336: DO NOT add the assembly target for Arm64 (ARM_SOURCES) until
c2goasm added the Arm64 support.
@@ -79,6 +79,9 @@ _lib/filter_uint32_avx2_amd64.s: _lib/filter_uint32.cc
_lib/filter_uint64_avx2_amd64.s: _lib/filter_uint64.cc
$(CXX) -std=c++17 -S $(C_FLAGS) $(ASM_FLAGS_AVX2) -mno-stackrealign
-fomit-frame-pointer $^ -o $@ ; $(PERL_FIXUP_ROTATE) $@
+_lib/filter_uint16_avx2_amd64.s: _lib/filter_uint16.cc
+ $(CXX) -std=c++17 -S $(C_FLAGS) $(ASM_FLAGS_AVX2) -mno-stackrealign
-fomit-frame-pointer $^ -o $@ ; $(PERL_FIXUP_ROTATE) $@
+
_lib/base_arithmetic_neon.s: _lib/base_arithmetic.cc
$(CXX) -std=c++17 -S $(C_FLAGS_NEON) $^ -o $@ ; $(PERL_FIXUP_ROTATE) $@
@@ -122,6 +125,10 @@ filter_uint64_avx2_amd64.s: _lib/filter_uint64_avx2_amd64.s
$(C2GOASM) -a -f $^ $@
perl -i -pe 's{^//\s*\+build\b.*}{//go:build go1.18 && amd64 && !noasm
&& !appengine}' $@
+filter_uint16_avx2_amd64.s: _lib/filter_uint16_avx2_amd64.s
+ $(C2GOASM) -a -f $^ $@
+ perl -i -pe 's{^//\s*\+build\b.*}{//go:build go1.18 && amd64 && !noasm
&& !appengine}' $@
+
clean:
rm -f $(INTEL_SOURCES)
rm -f $(addprefix _lib/,$(INTEL_SOURCES))
diff --git a/arrow/compute/internal/kernels/_lib/filter_uint16.cc
b/arrow/compute/internal/kernels/_lib/filter_uint16.cc
new file mode 100644
index 00000000..749f6db1
--- /dev/null
+++ b/arrow/compute/internal/kernels/_lib/filter_uint16.cc
@@ -0,0 +1,80 @@
+// 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 <arch.h>
+#include <immintrin.h>
+#include <stdint.h>
+
+extern "C" void FULL_NAME(filter_uint16)(const uint16_t* values,
+ const uint8_t* filter,
+ uint16_t* output,
+ const uint8_t* tables,
+ const int64_t length) {
+ const uint8_t* shuffle_masks = tables;
+ const uint8_t* popcount = tables + 4096;
+ int64_t output_length = 0;
+ const int64_t num_bytes = length / 8;
+
+ for (int64_t i = 0; i < num_bytes; ++i) {
+ const uint8_t mask = filter[i];
+ const uint16_t* input = values + i * 8;
+
+ if (mask == 0) {
+ continue;
+ }
+
+ if (mask == 0xff) {
+ const __m128i input_values = _mm_loadu_si128(
+ reinterpret_cast<const __m128i*>(input));
+ _mm_storeu_si128(reinterpret_cast<__m128i*>(output +
output_length), input_values);
+ output_length += 8;
+ continue;
+ }
+
+ const int count = popcount[mask];
+ const __m128i input_values = _mm_loadu_si128(
+ reinterpret_cast<const __m128i*>(input));
+ const __m128i shuffle = _mm_loadu_si128(
+ reinterpret_cast<const __m128i*>(shuffle_masks + mask * 16));
+ const __m128i compacted = _mm_shuffle_epi8(input_values, shuffle);
+
+ if (count >= 4) {
+ _mm_storel_epi64(reinterpret_cast<__m128i*>(output +
output_length), compacted);
+ output_length += 4;
+ if (count >= 7) {
+ output[output_length + 2] =
static_cast<uint16_t>(_mm_extract_epi16(compacted, 6));
+ }
+ if (count >= 6) {
+ output[output_length + 1] =
static_cast<uint16_t>(_mm_extract_epi16(compacted, 5));
+ }
+ if (count >= 5) {
+ output[output_length] =
static_cast<uint16_t>(_mm_extract_epi16(compacted, 4));
+ }
+ output_length += count - 4;
+ } else {
+ if (count >= 3) {
+ output[output_length + 2] =
static_cast<uint16_t>(_mm_extract_epi16(compacted, 2));
+ }
+ if (count >= 2) {
+ output[output_length + 1] =
static_cast<uint16_t>(_mm_extract_epi16(compacted, 1));
+ }
+ if (count >= 1) {
+ output[output_length] =
static_cast<uint16_t>(_mm_extract_epi16(compacted, 0));
+ }
+ output_length += count;
+ }
+ }
+}
diff --git a/arrow/compute/internal/kernels/_lib/filter_uint16_avx2_amd64.s
b/arrow/compute/internal/kernels/_lib/filter_uint16_avx2_amd64.s
new file mode 100644
index 00000000..b73dcacc
--- /dev/null
+++ b/arrow/compute/internal/kernels/_lib/filter_uint16_avx2_amd64.s
@@ -0,0 +1,92 @@
+ .intel_syntax noprefix
+ .file "filter_uint16.cc"
+ .text
+ .globl filter_uint16_avx2 # -- Begin function
filter_uint16_avx2
+ .p2align 4
+ .type filter_uint16_avx2,@function
+filter_uint16_avx2: # @filter_uint16_avx2
+# %bb.0:
+ lea rax, [r8 + 7]
+ test r8, r8
+ cmovns rax, r8
+ cmp r8, 8
+ jl .LBB0_21
+# %bb.1:
+ sar rax, 3
+ xor r8d, r8d
+ xor r9d, r9d
+ jmp .LBB0_2
+ .p2align 4
+.LBB0_4: # in Loop: Header=BB0_2 Depth=1
+ vmovdqu xmm0, xmmword ptr [rdi]
+ vmovdqu xmmword ptr [rdx + 2*r8], xmm0
+ add r8, 8
+.LBB0_20: # in Loop: Header=BB0_2 Depth=1
+ inc r9
+ add rdi, 16
+ cmp rax, r9
+ je .LBB0_21
+.LBB0_2: # =>This Inner Loop Header: Depth=1
+ movzx r11d, byte ptr [rsi + r9]
+ test r11d, r11d
+ je .LBB0_20
+# %bb.3: # in Loop: Header=BB0_2 Depth=1
+ cmp r11d, 255
+ je .LBB0_4
+# %bb.5: # in Loop: Header=BB0_2 Depth=1
+ movzx r10d, byte ptr [rcx + r11 + 4096]
+ shl r11d, 4
+ vmovdqu xmm0, xmmword ptr [rdi]
+ vpshufb xmm0, xmm0, xmmword ptr [rcx + r11]
+ cmp r10, 4
+ jb .LBB0_13
+# %bb.6: # in Loop: Header=BB0_2 Depth=1
+ vmovq qword ptr [rdx + 2*r8], xmm0
+ cmp r10b, 7
+ jb .LBB0_8
+# %bb.7: # in Loop: Header=BB0_2 Depth=1
+ vpextrw word ptr [rdx + 2*r8 + 12], xmm0, 6
+ jmp .LBB0_10
+.LBB0_13: # in Loop: Header=BB0_2 Depth=1
+ cmp r10d, 3
+ jne .LBB0_15
+# %bb.14: # in Loop: Header=BB0_2 Depth=1
+ vpextrw word ptr [rdx + 2*r8 + 4], xmm0, 2
+ vpextrw word ptr [rdx + 2*r8 + 2], xmm0, 1
+ jmp .LBB0_18
+.LBB0_8: # in Loop: Header=BB0_2 Depth=1
+ cmp r10, 4
+ je .LBB0_12
+# %bb.9: # in Loop: Header=BB0_2 Depth=1
+ cmp r10d, 6
+ jne .LBB0_11
+.LBB0_10: # in Loop: Header=BB0_2 Depth=1
+ vpextrw word ptr [rdx + 2*r8 + 10], xmm0, 5
+.LBB0_11: # in Loop: Header=BB0_2 Depth=1
+ vpextrw word ptr [rdx + 2*r8 + 8], xmm0, 4
+.LBB0_12: # in Loop: Header=BB0_2 Depth=1
+ add r8, 4
+ add r10d, -4
+ jmp .LBB0_19
+.LBB0_15: # in Loop: Header=BB0_2 Depth=1
+ cmp r10b, 2
+ jb .LBB0_17
+# %bb.16: # in Loop: Header=BB0_2 Depth=1
+ vpextrw word ptr [rdx + 2*r8 + 2], xmm0, 1
+ jmp .LBB0_18
+.LBB0_17: # in Loop: Header=BB0_2 Depth=1
+ test r10, r10
+ je .LBB0_19
+.LBB0_18: # in Loop: Header=BB0_2 Depth=1
+ vpextrw word ptr [rdx + 2*r8], xmm0, 0
+.LBB0_19: # in Loop: Header=BB0_2 Depth=1
+ add r8, r10
+ jmp .LBB0_20
+.LBB0_21:
+ ret
+.Lfunc_end0:
+ .size filter_uint16_avx2, .Lfunc_end0-filter_uint16_avx2
+ # -- End function
+ .ident "Apple clang version 21.0.0 (clang-2100.1.1.101)"
+ .section ".note.GNU-stack","",@progbits
+ .addrsig
diff --git a/arrow/compute/internal/kernels/filter_uint16.go
b/arrow/compute/internal/kernels/filter_uint16.go
new file mode 100644
index 00000000..51d61844
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16.go
@@ -0,0 +1,69 @@
+// 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 kernels
+
+import "math/bits"
+
+func makeFilterUint16Tables() (tables [4352]byte) {
+ for mask := 0; mask < 256; mask++ {
+ pos := 0
+ for lane := 0; lane < 8; lane++ {
+ if mask&(1<<uint(lane)) == 0 {
+ continue
+ }
+ tables[mask*16+pos] = byte(lane * 2)
+ tables[mask*16+pos+1] = byte(lane*2 + 1)
+ pos += 2
+ }
+ for ; pos < 16; pos++ {
+ tables[mask*16+pos] = 0x80
+ }
+ tables[4096+mask] = byte(bits.OnesCount8(uint8(mask)))
+ }
+ return tables
+}
+
+func filterUint16VectorInput(filterData []byte, filterOffset, length int64)
([]byte, bool) {
+ if length < 64 || length%8 != 0 || filterOffset%8 != 0 {
+ return nil, false
+ }
+
+ numBytes := length / 8
+ filterByteOffset := filterOffset / 8
+ if filterByteOffset < 0 || filterByteOffset+numBytes >
int64(len(filterData)) {
+ return nil, false
+ }
+ filterBytes := filterData[filterByteOffset : filterByteOffset+numBytes]
+
+ const (
+ sampleBytes = 64
+ minMixed = 4
+ )
+ mixedBytes := 0
+ for i := int64(0); i < int64(len(filterBytes)) && i < sampleBytes; i++ {
+ mask := filterBytes[i]
+ if mask != 0 && mask != 0xff {
+ mixedBytes++
+ if mixedBytes == minMixed {
+ return filterBytes, true
+ }
+ }
+ }
+ return nil, false
+}
diff --git a/arrow/compute/internal/kernels/filter_uint16_arch_test.go
b/arrow/compute/internal/kernels/filter_uint16_arch_test.go
new file mode 100644
index 00000000..1eaf70f4
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16_arch_test.go
@@ -0,0 +1,155 @@
+// 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 && (amd64 || arm64) && !noasm && !appengine
+
+package kernels
+
+import "testing"
+
+type filterUint16Func func(values []uint16, output []uint16, filter []byte,
filterOffset, length int64) bool
+
+func testFilterUint16AllMasks(t *testing.T, filter filterUint16Func) {
+ t.Helper()
+
+ const (
+ filterBytes = 256
+ length = filterBytes * 8
+ )
+ values := make([]uint16, length)
+ filterData := make([]byte, filterBytes)
+ for i := range values {
+ values[i] = uint16(i*977 + 0x8011)
+ }
+ for i := range filterData {
+ filterData[i] = byte(i)
+ }
+
+ expected := make([]uint16, 0, length)
+ for i, mask := range filterData {
+ for lane := 0; lane < 8; lane++ {
+ if mask&(1<<uint(lane)) != 0 {
+ expected = append(expected, values[i*8+lane])
+ }
+ }
+ }
+
+ backing := make([]uint16, len(expected)+1)
+ backing[len(expected)] = 0xdead
+ if !filter(values, backing[:len(expected)], filterData, 0, length) {
+ t.Fatal("filterUint16 did not select the mixed-mask path")
+ }
+ for i, want := range expected {
+ if got := backing[i]; got != want {
+ t.Fatalf("output[%d] = %#x, want %#x", i, got, want)
+ }
+ }
+ if got := backing[len(expected)]; got != 0xdead {
+ t.Fatalf("sentinel = %#x, want %#x", got, uint16(0xdead))
+ }
+}
+
+func testFilterUint16AlignedOffset(t *testing.T, filter filterUint16Func) {
+ t.Helper()
+
+ const (
+ offset = 8
+ length = 64
+ )
+ values := make([]uint16, length)
+ for i := range values {
+ values[i] = uint16(i*31 + 0x8000)
+ }
+ filterData := make([]byte, offset/8+length/8)
+ for i := range filterData[offset/8:] {
+ filterData[offset/8+i] = 0x55
+ }
+
+ output := make([]uint16, length/2)
+ if !filter(values, output, filterData, offset, length) {
+ t.Fatal("filterUint16 did not select the aligned-offset path")
+ }
+ for i, got := range output {
+ want := values[2*i]
+ if got != want {
+ t.Fatalf("output[%d] = %#x, want %#x", i, got, want)
+ }
+ }
+}
+
+func testFilterUint16UnalignedValues(t *testing.T, filter filterUint16Func) {
+ t.Helper()
+
+ const length = 64
+ valuesBacking := make([]uint16, length+3)
+ values := valuesBacking[3:]
+ for i := range values {
+ values[i] = uint16(i*31 + 0x8000)
+ }
+ filterData := make([]byte, length/8)
+ for i := range filterData {
+ filterData[i] = 0x55
+ }
+
+ output := make([]uint16, length/2)
+ if !filter(values, output, filterData, 0, length) {
+ t.Fatal("filterUint16 did not select the unaligned-values path")
+ }
+ for i, got := range output {
+ want := values[2*i]
+ if got != want {
+ t.Fatalf("output[%d] = %#x, want %#x", i, got, want)
+ }
+ }
+}
+
+func testFilterUint16Guards(t *testing.T, filter filterUint16Func) {
+ t.Helper()
+
+ values := make([]uint16, 128)
+ mixed := make([]byte, 16)
+ for i := range mixed {
+ mixed[i] = 0x55
+ }
+ output := make([]uint16, 64)
+ if filter(values[:63], output, mixed[:8], 0, 64) {
+ t.Fatal("filterUint16 accepted values shorter than length")
+ }
+
+ tests := []struct {
+ name string
+ filter []byte
+ offset int64
+ length int64
+ want bool
+ }{
+ {name: "short", filter: mixed, length: 56},
+ {name: "non-multiple-of-eight", filter: mixed, length: 71},
+ {name: "unaligned-filter-offset", filter: mixed, offset: 3,
length: 64},
+ {name: "negative-filter-offset", filter: mixed, offset: -8,
length: 64},
+ {name: "all-false", filter: make([]byte, 8), length: 64},
+ {name: "all-true", filter: []byte{0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff}, length: 64},
+ {name: "too-few-mixed-bytes", filter: []byte{0x55, 0x55, 0x55,
0, 0, 0, 0, 0}, length: 64},
+ {name: "out-of-bounds-filter", filter: mixed[:4], length: 64},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if got := filter(values, output, tt.filter, tt.offset,
tt.length); got != tt.want {
+ t.Fatalf("filterUint16() = %t, want %t", got,
tt.want)
+ }
+ })
+ }
+}
diff --git a/arrow/compute/internal/kernels/filter_uint16_avx2_amd64.go
b/arrow/compute/internal/kernels/filter_uint16_avx2_amd64.go
new file mode 100644
index 00000000..518c2b82
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16_avx2_amd64.go
@@ -0,0 +1,50 @@
+// 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 && amd64 && !noasm && !appengine
+
+package kernels
+
+import (
+ "unsafe"
+
+ "golang.org/x/sys/cpu"
+)
+
+var filterUint16Avx2Tables = makeFilterUint16Tables()
+
+//go:noescape
+func _filter_uint16_avx2(values, filter, output, tables unsafe.Pointer, length
int64)
+
+func filterUint16Avx2(values []uint16, output []uint16, filterData []byte,
filterOffset, length int64) bool {
+ if !cpu.X86.HasAVX2 || length > int64(len(values)) || len(output) == 0 {
+ return false
+ }
+
+ filterBytes, ok := filterUint16VectorInput(filterData, filterOffset,
length)
+ if !ok {
+ return false
+ }
+
+ _filter_uint16_avx2(
+ unsafe.Pointer(unsafe.SliceData(values)),
+ unsafe.Pointer(unsafe.SliceData(filterBytes)),
+ unsafe.Pointer(unsafe.SliceData(output)),
+ unsafe.Pointer(unsafe.SliceData(filterUint16Avx2Tables[:])),
+ length,
+ )
+ return true
+}
diff --git a/arrow/compute/internal/kernels/filter_uint16_avx2_amd64.s
b/arrow/compute/internal/kernels/filter_uint16_avx2_amd64.s
new file mode 100644
index 00000000..817a9712
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16_avx2_amd64.s
@@ -0,0 +1,93 @@
+//go:build go1.18 && amd64 && !noasm && !appengine
+// AUTO-GENERATED BY C2GOASM -- DO NOT EDIT
+
+TEXT ·_filter_uint16_avx2(SB), $0-40
+
+ MOVQ values+0(FP), DI
+ MOVQ filter+8(FP), SI
+ MOVQ output+16(FP), DX
+ MOVQ tables+24(FP), CX
+ MOVQ length+32(FP), R8
+
+ LONG $0x07408d49 // lea rax, [r8 + 7]
+ WORD $0x854d; BYTE $0xc0 // test r8, r8
+ LONG $0xc0490f49 // cmovns rax, r8
+ LONG $0x08f88349 // cmp r8, 8
+ JL LBB0_21
+ LONG $0x03f8c148 // sar rax, 3
+ WORD $0x3145; BYTE $0xc0 // xor r8d, r8d
+ WORD $0x3145; BYTE $0xc9 // xor r9d, r9d
+ JMP LBB0_2
+
+LBB0_4:
+ LONG $0x076ffac5 // vmovdqu xmm0, oword [rdi]
+ LONG $0x7f7aa1c4; WORD $0x4204 // vmovdqu oword [rdx + 2*r8], xmm0
+ LONG $0x08c08349 // add r8, 8
+
+LBB0_20:
+ WORD $0xff49; BYTE $0xc1 // inc r9
+ LONG $0x10c78348 // add rdi, 16
+ WORD $0x394c; BYTE $0xc8 // cmp rax, r9
+ JE LBB0_21
+
+LBB0_2:
+ LONG $0x1cb60f46; BYTE $0x0e // movzx r11d, byte [rsi
+ r9]
+ WORD $0x8545; BYTE $0xdb // test r11d, r11d
+ JE LBB0_20
+ LONG $0xfffb8141; WORD $0x0000; BYTE $0x00 // cmp r11d, 255
+ JE LBB0_4
+ QUAD $0x0010001994b60f46; BYTE $0x00 // movzx r10d, byte [rcx
+ r11 + 4096]
+ LONG $0x04e3c141 // shl r11d, 4
+ LONG $0x076ffac5 // vmovdqu xmm0, oword
[rdi]
+ LONG $0x0079a2c4; WORD $0x1904 // vpshufb xmm0, xmm0,
oword [rcx + r11]
+ LONG $0x04fa8349 // cmp r10, 4
+ JB LBB0_13
+ LONG $0xd679a1c4; WORD $0x4204 // vmovq qword [rdx +
2*r8], xmm0
+ LONG $0x07fa8041 // cmp r10b, 7
+ JB LBB0_8
+ QUAD $0x060c42441579a3c4 // vpextrw word [rdx +
2*r8 + 12], xmm0, 6
+ JMP LBB0_10
+
+LBB0_13:
+ LONG $0x03fa8341 // cmp r10d, 3
+ JNE LBB0_15
+ QUAD $0x020442441579a3c4 // vpextrw word [rdx + 2*r8 + 4], xmm0, 2
+ QUAD $0x010242441579a3c4 // vpextrw word [rdx + 2*r8 + 2], xmm0, 1
+ JMP LBB0_18
+
+LBB0_8:
+ LONG $0x04fa8349 // cmp r10, 4
+ JE LBB0_12
+ LONG $0x06fa8341 // cmp r10d, 6
+ JNE LBB0_11
+
+LBB0_10:
+ QUAD $0x050a42441579a3c4 // vpextrw word [rdx + 2*r8 + 10], xmm0, 5
+
+LBB0_11:
+ QUAD $0x040842441579a3c4 // vpextrw word [rdx + 2*r8 + 8], xmm0, 4
+
+LBB0_12:
+ LONG $0x04c08349 // add r8, 4
+ LONG $0xfcc28341 // add r10d, -4
+ JMP LBB0_19
+
+LBB0_15:
+ LONG $0x02fa8041 // cmp r10b, 2
+ JB LBB0_17
+ QUAD $0x010242441579a3c4 // vpextrw word [rdx + 2*r8 + 2], xmm0, 1
+ JMP LBB0_18
+
+LBB0_17:
+ WORD $0x854d; BYTE $0xd2 // test r10, r10
+ JE LBB0_19
+
+LBB0_18:
+ LONG $0x1579a3c4; WORD $0x4204; BYTE $0x00 // vpextrw word [rdx +
2*r8], xmm0, 0
+
+LBB0_19:
+ WORD $0x014d; BYTE $0xd0 // add r8, r10
+ JMP LBB0_20
+
+LBB0_21:
+ RET
diff --git a/arrow/compute/internal/kernels/filter_uint16_avx2_amd64_test.go
b/arrow/compute/internal/kernels/filter_uint16_avx2_amd64_test.go
new file mode 100644
index 00000000..97e78ce1
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16_avx2_amd64_test.go
@@ -0,0 +1,53 @@
+// 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 && amd64 && !noasm && !appengine
+
+package kernels
+
+import (
+ "testing"
+
+ "golang.org/x/sys/cpu"
+)
+
+func TestFilterUint16Avx2AllMasks(t *testing.T) {
+ if !cpu.X86.HasAVX2 {
+ t.Skip("AVX2 is not available")
+ }
+ testFilterUint16AllMasks(t, filterUint16Avx2)
+}
+
+func TestFilterUint16Avx2AlignedOffset(t *testing.T) {
+ if !cpu.X86.HasAVX2 {
+ t.Skip("AVX2 is not available")
+ }
+ testFilterUint16AlignedOffset(t, filterUint16Avx2)
+}
+
+func TestFilterUint16Avx2UnalignedValues(t *testing.T) {
+ if !cpu.X86.HasAVX2 {
+ t.Skip("AVX2 is not available")
+ }
+ testFilterUint16UnalignedValues(t, filterUint16Avx2)
+}
+
+func TestFilterUint16Avx2Guards(t *testing.T) {
+ if !cpu.X86.HasAVX2 {
+ t.Skip("AVX2 is not available")
+ }
+ testFilterUint16Guards(t, filterUint16Avx2)
+}
diff --git a/arrow/compute/internal/kernels/filter_uint16_avx2_noasm.go
b/arrow/compute/internal/kernels/filter_uint16_avx2_noasm.go
new file mode 100644
index 00000000..17e52abb
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16_avx2_noasm.go
@@ -0,0 +1,23 @@
+// 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 || !amd64 || appengine)
+
+package kernels
+
+func filterUint16Avx2([]uint16, []uint16, []byte, int64, int64) bool {
+ return false
+}
diff --git a/arrow/compute/internal/kernels/filter_uint16_neon_arm64.go
b/arrow/compute/internal/kernels/filter_uint16_neon_arm64.go
new file mode 100644
index 00000000..f2c9c3fe
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16_neon_arm64.go
@@ -0,0 +1,50 @@
+// 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"
+
+ "golang.org/x/sys/cpu"
+)
+
+var filterUint16NeonTables = makeFilterUint16Tables()
+
+//go:noescape
+func _filter_uint16_neon(values, filter, output, tables unsafe.Pointer, length
int64)
+
+func filterUint16Neon(values []uint16, output []uint16, filterData []byte,
filterOffset, length int64) bool {
+ if !cpu.ARM64.HasASIMD || length > int64(len(values)) || len(output) ==
0 {
+ return false
+ }
+
+ filterBytes, ok := filterUint16VectorInput(filterData, filterOffset,
length)
+ if !ok {
+ return false
+ }
+
+ _filter_uint16_neon(
+ unsafe.Pointer(unsafe.SliceData(values)),
+ unsafe.Pointer(unsafe.SliceData(filterBytes)),
+ unsafe.Pointer(unsafe.SliceData(output)),
+ unsafe.Pointer(unsafe.SliceData(filterUint16NeonTables[:])),
+ length,
+ )
+ return true
+}
diff --git a/arrow/compute/internal/kernels/filter_uint16_neon_arm64.s
b/arrow/compute/internal/kernels/filter_uint16_neon_arm64.s
new file mode 100644
index 00000000..4b956b16
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16_neon_arm64.s
@@ -0,0 +1,120 @@
+// 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
+
+#include "textflag.h"
+
+// Each filter byte selects up to eight uint16 values. TBL compacts one
+// sixteen-byte input vector, and stores write exactly the selected bytes.
+// func _filter_uint16_neon(values, filter, output, tables unsafe.Pointer,
length int64)
+TEXT ·_filter_uint16_neon(SB), NOSPLIT|NOFRAME, $0-40
+ MOVD values+0(FP), R0
+ MOVD filter+8(FP), R1
+ MOVD output+16(FP), R2
+ MOVD tables+24(FP), R3
+ MOVD length+32(FP), R4
+
+ LSR $3, R4, R4
+ ADD $4096, R3, R8
+
+filter_uint16_loop:
+ MOVBU (R1), R5
+ ADD $1, R1
+ CBZ R5, filter_uint16_next
+ CMPW $255, R5
+ BEQ filter_uint16_all
+
+ MOVBU (R8)(R5<<0), R6
+ CBZ R6, filter_uint16_next
+
+ LSL $4, R5, R7
+ ADD R3, R7, R7
+ VLD1 (R0), [V0.B16]
+ VLD1 (R7), [V1.B16]
+ VTBL V1.B16, [V0.B16], V2.B16
+
+ CMPW $4, R6
+ BLT filter_uint16_store_small
+ VST1 [V2.H4], (R2)
+ ADD $8, R2
+ SUB $4, R6
+ CBZ R6, filter_uint16_next
+ CMPW $1, R6
+ BEQ filter_uint16_store_high1
+ CMPW $2, R6
+ BEQ filter_uint16_store_high2
+ VMOV V2.H[4], R9
+ MOVH R9, (R2)
+ VMOV V2.H[5], R9
+ MOVH R9, 2(R2)
+ VMOV V2.H[6], R9
+ MOVH R9, 4(R2)
+ ADD $6, R2
+ B filter_uint16_next
+
+filter_uint16_store_high1:
+ VMOV V2.H[4], R9
+ MOVH R9, (R2)
+ ADD $2, R2
+ B filter_uint16_next
+
+filter_uint16_store_high2:
+ VMOV V2.H[4], R9
+ MOVH R9, (R2)
+ VMOV V2.H[5], R9
+ MOVH R9, 2(R2)
+ ADD $4, R2
+ B filter_uint16_next
+
+filter_uint16_store_small:
+ CMPW $1, R6
+ BEQ filter_uint16_store1
+ CMPW $2, R6
+ BEQ filter_uint16_store2
+ VMOV V2.H[0], R9
+ MOVH R9, (R2)
+ VMOV V2.H[1], R9
+ MOVH R9, 2(R2)
+ VMOV V2.H[2], R9
+ MOVH R9, 4(R2)
+ ADD $6, R2
+ B filter_uint16_next
+
+filter_uint16_store1:
+ VMOV V2.H[0], R9
+ MOVH R9, (R2)
+ ADD $2, R2
+ B filter_uint16_next
+
+filter_uint16_store2:
+ VMOV V2.H[0], R9
+ MOVH R9, (R2)
+ VMOV V2.H[1], R9
+ MOVH R9, 2(R2)
+ ADD $4, R2
+ B filter_uint16_next
+
+filter_uint16_all:
+ VLD1 (R0), [V0.B16]
+ VST1 [V0.B16], (R2)
+ ADD $16, R2
+
+filter_uint16_next:
+ ADD $16, R0
+ SUBS $1, R4, R4
+ BNE filter_uint16_loop
+ RET
diff --git a/arrow/compute/internal/kernels/filter_uint16_neon_arm64_test.go
b/arrow/compute/internal/kernels/filter_uint16_neon_arm64_test.go
new file mode 100644
index 00000000..e46a0f10
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16_neon_arm64_test.go
@@ -0,0 +1,53 @@
+// 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 (
+ "testing"
+
+ "golang.org/x/sys/cpu"
+)
+
+func TestFilterUint16NeonAllMasks(t *testing.T) {
+ if !cpu.ARM64.HasASIMD {
+ t.Skip("ARM64 SIMD is not available")
+ }
+ testFilterUint16AllMasks(t, filterUint16Neon)
+}
+
+func TestFilterUint16NeonAlignedOffset(t *testing.T) {
+ if !cpu.ARM64.HasASIMD {
+ t.Skip("ARM64 SIMD is not available")
+ }
+ testFilterUint16AlignedOffset(t, filterUint16Neon)
+}
+
+func TestFilterUint16NeonUnalignedValues(t *testing.T) {
+ if !cpu.ARM64.HasASIMD {
+ t.Skip("ARM64 SIMD is not available")
+ }
+ testFilterUint16UnalignedValues(t, filterUint16Neon)
+}
+
+func TestFilterUint16NeonGuards(t *testing.T) {
+ if !cpu.ARM64.HasASIMD {
+ t.Skip("ARM64 SIMD is not available")
+ }
+ testFilterUint16Guards(t, filterUint16Neon)
+}
diff --git a/arrow/compute/internal/kernels/filter_uint16_neon_noasm.go
b/arrow/compute/internal/kernels/filter_uint16_neon_noasm.go
new file mode 100644
index 00000000..265cf13d
--- /dev/null
+++ b/arrow/compute/internal/kernels/filter_uint16_neon_noasm.go
@@ -0,0 +1,23 @@
+// 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
+
+func filterUint16Neon([]uint16, []uint16, []byte, int64, int64) bool {
+ return false
+}
diff --git a/arrow/compute/internal/kernels/vector_selection.go
b/arrow/compute/internal/kernels/vector_selection.go
index 8ea29377..174148f2 100644
--- a/arrow/compute/internal/kernels/vector_selection.go
+++ b/arrow/compute/internal/kernels/vector_selection.go
@@ -495,6 +495,14 @@ func PrimitiveFilter(ctx *exec.KernelCtx, batch
*exec.ExecSpan, out *exec.ExecRe
return nil
}
}
+ if bitWidth == 16 && values.Nulls == 0 && filter.Nulls == 0 {
+ valuesData := exec.GetSpanValues[uint16](values, 1)
+ outData := exec.GetSpanValues[uint16](out, 1)
+ if filterUint16Avx2(valuesData, outData, filter.Buffers[1].Buf,
filter.Offset, values.Len) ||
+ filterUint16Neon(valuesData, outData,
filter.Buffers[1].Buf, filter.Offset, values.Len) {
+ return nil
+ }
+ }
if bitWidth == 64 && values.Nulls == 0 && filter.Nulls == 0 {
valuesData := exec.GetSpanValues[uint64](values, 1)
outData := exec.GetSpanValues[uint64](out, 1)
diff --git a/arrow/compute/internal/kernels/vector_selection_test.go
b/arrow/compute/internal/kernels/vector_selection_test.go
index f4c69338..0721f6b7 100644
--- a/arrow/compute/internal/kernels/vector_selection_test.go
+++ b/arrow/compute/internal/kernels/vector_selection_test.go
@@ -20,6 +20,7 @@
package kernels
import (
+ "context"
"testing"
"github.com/apache/arrow-go/v18/arrow"
@@ -112,6 +113,61 @@ func assertTakeIndices[T arrow.IntType | arrow.UintType](t
*testing.T, data arro
require.Equal(t, nulls, data.NullN())
}
+func TestPrimitiveFilterInt16MixedMaskOffsets(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ const (
+ offset = 8
+ length = 128
+ )
+ valuesBuilder := array.NewInt16Builder(mem)
+ valuesBuilder.Reserve(offset + length)
+ for i := 0; i < offset+length; i++ {
+ valuesBuilder.Append(int16(uint16(i*997 + 0x8123)))
+ }
+ valuesBase := valuesBuilder.NewInt16Array()
+ valuesBuilder.Release()
+ values := array.NewSlice(valuesBase, offset, offset+length)
+ valuesBase.Release()
+ defer values.Release()
+
+ filterBuilder := array.NewBooleanBuilder(mem)
+ filterBuilder.Reserve(offset + length)
+ for i := 0; i < offset+length; i++ {
+ filterBuilder.Append(i%5 == 0 || i%5 == 2)
+ }
+ filterBase := filterBuilder.NewBooleanArray()
+ filterBuilder.Release()
+ filter := array.NewSlice(filterBase, offset, offset+length)
+ filterBase.Release()
+ defer filter.Release()
+
+ var batch exec.ExecSpan
+ batch.Len = length
+ batch.Values = make([]exec.ExecValue, 2)
+ batch.Values[0].Array.SetMembers(values.Data())
+ batch.Values[1].Array.SetMembers(filter.Data())
+ ctx := &exec.KernelCtx{
+ Ctx: exec.WithAllocator(context.Background(), mem),
+ State: FilterState{NullSelection: DropNulls},
+ }
+ var out exec.ExecResult
+ require.NoError(t, PrimitiveFilter(ctx, &batch, &out))
+ defer out.Release()
+
+ got := exec.GetSpanValues[int16](&out, 1)
+ valuesData := exec.GetSpanValues[int16](&batch.Values[0].Array, 1)
+ position := 0
+ for i := offset; i < offset+length; i++ {
+ if i%5 == 0 || i%5 == 2 {
+ require.Equal(t, valuesData[i-offset], got[position])
+ position++
+ }
+ }
+ require.Equal(t, position, int(out.Len))
+}
+
func getTakeIndicesForTest[T arrow.IntType | arrow.UintType](mem
memory.Allocator, filter arrow.Array, nullSelect NullSelectionBehavior)
arrow.ArrayData {
var span exec.ArraySpan
span.SetMembers(filter.Data())
diff --git a/arrow/compute/vector_selection_bench_test.go
b/arrow/compute/vector_selection_bench_test.go
index 8bef0201..b0a5c930 100644
--- a/arrow/compute/vector_selection_bench_test.go
+++ b/arrow/compute/vector_selection_bench_test.go
@@ -14,7 +14,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//go:build go1.18
+//go:build go1.24
package compute_test
@@ -35,6 +35,10 @@ func BenchmarkFilterInt32MixedMasks(b *testing.B) {
benchmarkFilter32MixedMasks(b, makeFilterInt32BenchmarkInput)
}
+func BenchmarkFilterInt16MixedMasks(b *testing.B) {
+ benchmarkFilterMixedMasks(b, makeFilterInt16BenchmarkInput, 2)
+}
+
func BenchmarkFilterFloat32MixedMasks(b *testing.B) {
benchmarkFilter32MixedMasks(b, makeFilterFloat32BenchmarkInput)
}
@@ -66,6 +70,12 @@ func benchmarkFilterMixedMasks(b *testing.B,
name string
selected func(int) bool
}{
+ {name: "mixed-prefix-long-runs", selected: func(i int) bool {
+ if i < 32 {
+ return i%2 == 0
+ }
+ return (i/4096)%2 == 0
+ }},
{name: "alternating", selected: func(i int) bool { return i%2
== 0 }},
{name: "random25", selected: func(i int) bool { return
filterBenchmarkRandom(i, 25) }},
{name: "random50", selected: func(i int) bool { return
filterBenchmarkRandom(i, 50) }},
@@ -112,7 +122,6 @@ func BenchmarkFilterInt32FallbackControls(b *testing.B) {
}
for _, control := range controls {
- control := control
b.Run(control.name, func(b *testing.B) {
values, filter :=
makeFilterInt32BenchmarkInputWithOffset(b, control.size, control.offset,
func(i int) bool { return i%2 == 0 })
@@ -122,7 +131,40 @@ func BenchmarkFilterInt32FallbackControls(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(control.size * 4))
b.ResetTimer()
- for i := 0; i < b.N; i++ {
+ for b.Loop() {
+ result, err :=
compute.FilterArray(context.Background(), values, filter,
*compute.DefaultFilterOptions())
+ if err != nil {
+ b.Fatal(err)
+ }
+ benchmarkFilterOutputLength = result.Len()
+ result.Release()
+ }
+ })
+ }
+}
+
+func BenchmarkFilterInt16FallbackControls(b *testing.B) {
+ controls := []struct {
+ name string
+ size int
+ offset int64
+ }{
+ {name: "unaligned-filter-offset", size: 1 << 16, offset: 3},
+ {name: "short", size: 56},
+ {name: "non-multiple-of-eight", size: 1<<16 + 1},
+ }
+
+ for _, control := range controls {
+ b.Run(control.name, func(b *testing.B) {
+ values, filter :=
makeFilterInt16BenchmarkInputWithOffset(b, control.size, control.offset,
+ func(i int) bool { return i%2 == 0 })
+ defer values.Release()
+ defer filter.Release()
+
+ b.ReportAllocs()
+ b.SetBytes(int64(control.size * 2))
+ b.ResetTimer()
+ for b.Loop() {
result, err :=
compute.FilterArray(context.Background(), values, filter,
*compute.DefaultFilterOptions())
if err != nil {
b.Fatal(err)
@@ -134,6 +176,36 @@ func BenchmarkFilterInt32FallbackControls(b *testing.B) {
}
}
+func makeFilterInt16BenchmarkInput(b *testing.B, size int, selected func(int)
bool) (arrow.Array, arrow.Array) {
+ return makeFilterInt16BenchmarkInputWithOffset(b, size, 0, selected)
+}
+
+func makeFilterInt16BenchmarkInputWithOffset(b *testing.B, size int, offset
int64, selected func(int) bool) (arrow.Array, arrow.Array) {
+ b.Helper()
+ mem := memory.DefaultAllocator
+
+ valuesBuilder := array.NewInt16Builder(mem)
+ valuesBuilder.Reserve(size + int(offset))
+ for i := 0; i < size+int(offset); i++ {
+ valuesBuilder.Append(int16(i))
+ }
+ valuesBase := valuesBuilder.NewInt16Array()
+ valuesBuilder.Release()
+ values := array.NewSlice(valuesBase, offset, offset+int64(size))
+ valuesBase.Release()
+
+ filterBuilder := array.NewBooleanBuilder(mem)
+ filterBuilder.Reserve(size + int(offset))
+ for i := 0; i < size+int(offset); i++ {
+ filterBuilder.Append(selected(i))
+ }
+ filterBase := filterBuilder.NewBooleanArray()
+ filterBuilder.Release()
+ filter := array.NewSlice(filterBase, offset, offset+int64(size))
+ filterBase.Release()
+ return values, filter
+}
+
func makeFilterInt32BenchmarkInput(b *testing.B, size int, selected func(int)
bool) (arrow.Array, arrow.Array) {
return makeFilterInt32BenchmarkInputWithOffset(b, size, 0, selected)
}
diff --git a/arrow/compute/vector_selection_test.go
b/arrow/compute/vector_selection_test.go
index 0d9d9f04..1e9233e6 100644
--- a/arrow/compute/vector_selection_test.go
+++ b/arrow/compute/vector_selection_test.go
@@ -2079,6 +2079,53 @@ func TestFilterInt32MixedMaskOffsets(t *testing.T) {
}
}
+func TestFilterInt16MixedMaskOffsets(t *testing.T) {
+ mem := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer mem.AssertSize(t, 0)
+
+ const length = 128
+ for _, offset := range []int64{0, 3, 8} {
+ t.Run(fmt.Sprintf("offset=%d", offset), func(t *testing.T) {
+ valuesBuilder := array.NewInt16Builder(mem)
+ valuesBuilder.Reserve(int(offset) + length)
+ for i := int64(0); i < offset+length; i++ {
+ valuesBuilder.Append(int16(uint16(i*977 +
0x8011)))
+ }
+ valuesBase := valuesBuilder.NewInt16Array()
+ valuesBuilder.Release()
+ values := array.NewSlice(valuesBase, offset,
offset+length)
+ valuesBase.Release()
+ defer values.Release()
+
+ filterBuilder := array.NewBooleanBuilder(mem)
+ filterBuilder.Reserve(int(offset) + length)
+ for i := int64(0); i < offset+length; i++ {
+ filterBuilder.Append(i%5 == 0 || i%5 == 2)
+ }
+ filterBase := filterBuilder.NewBooleanArray()
+ filterBuilder.Release()
+ filter := array.NewSlice(filterBase, offset,
offset+length)
+ filterBase.Release()
+ defer filter.Release()
+
+ expectedBuilder := array.NewInt16Builder(mem)
+ for i := offset; i < offset+length; i++ {
+ if i%5 == 0 || i%5 == 2 {
+
expectedBuilder.Append(int16(uint16(i*977 + 0x8011)))
+ }
+ }
+ expected := expectedBuilder.NewInt16Array()
+ expectedBuilder.Release()
+ defer expected.Release()
+
+ actual, err :=
compute.FilterArray(context.Background(), values, filter,
*compute.DefaultFilterOptions())
+ require.NoError(t, err)
+ defer actual.Release()
+ assertArraysEqual(t, expected, actual)
+ })
+ }
+}
+
// Benchmark tests for Take operation with variable-length data
// These benchmarks test the performance improvements from buffer
pre-allocation
// in VarBinaryImpl for string/binary data reorganization (e.g., partitioning).