diff --git a/.github/workflows/benchmark-a32.yml b/.github/workflows/benchmark-a32.yml new file mode 100644 index 00000000..aef2c825 --- /dev/null +++ b/.github/workflows/benchmark-a32.yml @@ -0,0 +1,29 @@ +name: Benchmark A32 + +on: [push, pull_request, workflow_dispatch] + +jobs: + benchmark-a32: + name: Benchmark A32 (Armv7-A) on A64 Native + if: contains(toJSON(github.event.head_commit.message), 'Merge pull request ') == false + timeout-minutes: 30 + runs-on: ubuntu-24.04-arm + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download dependencies + run: | + sudo dpkg --add-architecture armhf + sudo apt-get update -q -y + sudo apt-get install -q -y libc6:armhf crossbuild-essential-armhf g++-arm-linux-gnueabihf + + - name: Build and Run movemask benchmark natively in A32 mode + run: | + echo "=== Optimized Implementation (VPADD) ===" + make clean > /dev/null + make bench-movemask CROSS_COMPILE=arm-linux-gnueabihf- EXEC_WRAPPER= + + echo "=== Original Implementation (VSRA Baseline) ===" + make clean > /dev/null + make bench-movemask CROSS_COMPILE=arm-linux-gnueabihf- EXEC_WRAPPER= CXXFLAGS="-DBENCH_ORIGINAL_MOVEMASK_EPI8" diff --git a/Makefile b/Makefile index f62bc05f..1399fa13 100644 --- a/Makefile +++ b/Makefile @@ -361,7 +361,20 @@ fuzz-clean: $(RM) -r $(FUZZ_CORPUS) $(RM) crash-* leak-* timeout-* oom-* -.PHONY: clean check check-main check-ieee754 check-nan check-aes check-ubsan check-asan check-strict-aliasing check-uninit check-macros check-differential generate-golden coverage-report indent ieee754 nan aes fuzz fuzz-verbose fuzz-clean +# Movemask benchmark +BENCH_MOVEMASK_SRC = tests/bench_movemask.cpp +BENCH_MOVEMASK_EXEC = tests/bench_movemask + +$(BENCH_MOVEMASK_EXEC): $(BENCH_MOVEMASK_SRC) sse2neon.h + $(CXX) -O3 $(ARCH_CFLAGS) $(CXXFLAGS) -I. -std=gnu++14 $(LDFLAGS) -o $@ $< + +bench-movemask: $(BENCH_MOVEMASK_EXEC) +ifeq ($(processor),$(filter $(processor),aarch64 arm64 arm armv7l)) + $(CC) $(ARCH_CFLAGS) -c sse2neon.h +endif + $(EXEC_WRAPPER) $^ + +.PHONY: clean check check-main check-ieee754 check-nan check-aes check-ubsan check-asan check-strict-aliasing check-uninit check-macros check-differential generate-golden coverage-report indent ieee754 nan aes fuzz fuzz-verbose fuzz-clean bench-movemask clean: $(RM) $(OBJS) $(EXEC) $(deps) sse2neon.h.gch $(RM) $(IEEE754_OBJS) $(IEEE754_EXEC) $(ieee754_deps) @@ -369,6 +382,7 @@ clean: $(RM) $(AES_OBJS) $(AES_EXEC) $(aes_deps) $(RM) $(DIFFERENTIAL_OBJS) $(DIFFERENTIAL_EXEC) $(differential_deps) $(RM) $(FUZZ_EXEC) + $(RM) $(BENCH_MOVEMASK_EXEC) -include $(deps) -include $(ieee754_deps) diff --git a/perf-tier.md b/perf-tier.md index 56cc48b0..caa247c0 100644 --- a/perf-tier.md +++ b/perf-tier.md @@ -33,11 +33,11 @@ Cycle estimates based on ARM Cortex-A72 (ARMv8-A) Software Optimization Guide. | Metric | Value | |--------|-------| -| Total SSE Intrinsics | 468 | -| Direct Mappings (T1) | 367 (78.4%) | -| Moderate Emulation (T2-T3) | 87 (18.6%) | -| Complex Emulation (T4) | 14 (3.0%) | -| Avg NEON Ops/Intrinsic | 1.89 | +| Total SSE Intrinsics | 485 | +| Direct Mappings (T1) | 314 (64.7%) | +| Moderate Emulation (T2-T3) | 119 (24.5%) | +| Complex Emulation (T4) | 52 (10.7%) | +| Avg NEON Ops/Intrinsic | 2.90 | ### Performance Tiers @@ -61,167 +61,173 @@ algorithms when porting performance-critical code. | Intrinsic | NEON Ops | Notes | |-----------|----------|-------| -| `_mm_mpsadbw_epu8` | 22 | SAD computation, very expensive | +| `_mm_mpsadbw_epu8` | 37 | SAD computation, very expensive | +| `_mm_maddubs_epi16` | 22 | Multiply-add with widening | +| `_mm_round_ps` | 22 | Rounding modes emulation | +| `_mm_aesenclast_si128` | 20 | Use HW crypto when available | +| `_mm_aesdeclast_si128` | 20 | Use HW crypto when available | +| `_mm_cvtps_epi32` | 17 | | +| `_mm_cvttpd_pi32` | 17 | | +| `_mm_minpos_epu16` | 17 | Horizontal minimum search | +| `_mm_movemask_epi8` | 16 | | | `_mm_sqrt_ps` | 15 | Newton-Raphson refinement | -| `_mm_cvttpd_pi32` | 14 | | +| `_mm_dp_ps` | 14 | | | `_mm_rsqrt_ps` | 13 | Newton-Raphson refinement | | `_mm_aesdec_si128` | 12 | Use HW crypto when available | -| `_mm_dp_ps` | 9 | | -| `_mm_minpos_epu16` | 9 | Horizontal minimum search | -| `_mm_aesenc_si128` | 9 | Use HW crypto when available | -| `_mm_aesimc_si128` | 9 | Use HW crypto when available | -| `_mm_dp_pd` | 4 | | -| `_mm_shuffle_epi8` | 3 | | -| `_mm_shuffle_pi8` | 3 | | -| `_mm_aesenclast_si128` | 3 | Use HW crypto when available | -| `_mm_aesdeclast_si128` | 3 | Use HW crypto when available | +| `_mm_aesimc_si128` | 11 | Use HW crypto when available | +| `_mm_shuffle_epi8` | 10 | | ### Efficient Intrinsics (T1 - Single NEON Instruction) These intrinsics map directly to single NEON instructions: -- Arithmetic: `_mm_abs_epi16`, `_mm_abs_epi32`, `_mm_abs_epi8`, `_mm_abs_pi16`, `_mm_abs_pi32`, `_mm_abs_pi8`, `_mm_add_epi16`, `_mm_add_epi32`, ... (+29 more) -- Comparison: `_mm_cmpeq_epi16`, `_mm_cmpeq_epi32`, `_mm_cmpeq_epi64`, `_mm_cmpeq_epi8`, `_mm_cmpeq_pd`, `_mm_cmpeq_ps`, `_mm_cmpgt_epi16`, `_mm_cmpgt_epi32`, ... (+9 more) -- Logical: `_mm_and_pd`, `_mm_and_ps`, `_mm_and_si128`, `_mm_andnot_pd`, `_mm_andnot_ps`, `_mm_andnot_si128`, `_mm_floor_pd`, `_mm_floor_ps`, ... (+18 more) -- Load/Store: `_mm_load1_pd`, `_mm_load1_ps`, `_mm_load_pd`, `_mm_load_ps`, `_mm_load_si128`, `_mm_loadu_ps`, `_mm_loadu_si128`, `_mm_set1_epi16`, ... (+24 more) -- Conversion: `_mm_cvt_ps2pi`, `_mm_cvt_si2ss`, `_mm_cvt_ss2si`, `_mm_cvtepi32_ps`, `_mm_cvtps_epi32`, `_mm_cvtps_pi16`, `_mm_cvtsd_f64`, `_mm_cvtsd_si32`, ... (+13 more) -- Math: `_mm_ceil_pd`, `_mm_ceil_ps`, `_mm_floor_pd`, `_mm_floor_ps`, `_mm_max_epi16`, `_mm_max_epi32`, `_mm_max_epi8`, `_mm_max_epu16`, ... (+13 more) +- Arithmetic: `_mm_abs_epi16`, `_mm_abs_epi32`, `_mm_abs_epi8`, `_mm_abs_pi16`, `_mm_abs_pi32`, `_mm_abs_pi8`, `_mm_add_epi16`, `_mm_add_epi32`, ... (+24 more) +- Comparison: `_mm_cmpeq_epi16`, `_mm_cmpeq_epi32`, `_mm_cmpeq_epi8`, `_mm_cmpeq_ps`, `_mm_cmpgt_epi16`, `_mm_cmpgt_epi32`, `_mm_cmpgt_epi8`, `_mm_cmpgt_ps`, ... (+4 more) +- Logical: `_mm_and_pd`, `_mm_and_ps`, `_mm_and_si128`, `_mm_andnot_pd`, `_mm_andnot_ps`, `_mm_andnot_si128`, `_mm_or_pd`, `_mm_or_ps`, ... (+15 more) +- Load/Store: `_mm_load1_ps`, `_mm_load_ps`, `_mm_load_si128`, `_mm_loadu_ps`, `_mm_loadu_si128`, `_mm_set1_epi16`, `_mm_set1_epi32`, `_mm_set1_epi64`, ... (+19 more) +- Conversion: `_mm_cvt_si2ss`, `_mm_cvtepi32_ps`, `_mm_cvtps_pi16`, `_mm_cvtsd_si32`, `_mm_cvtsd_si64`, `_mm_cvtsi128_si32`, `_mm_cvtsi128_si64`, `_mm_cvtsi64_ss`, ... (+6 more) +- Math: `_mm_max_epi16`, `_mm_max_epi32`, `_mm_max_epi8`, `_mm_max_epu16`, `_mm_max_epu32`, `_mm_max_epu8`, `_mm_max_pi16`, `_mm_max_pu8`, ... (+8 more) ### Complete Tier Classification
Click to expand full list -#### Tier 1 (367 intrinsics) +#### Tier 1 (314 intrinsics) `_mm_abs_epi16`, `_mm_abs_epi32`, `_mm_abs_epi8`, `_mm_abs_pi16` `_mm_abs_pi32`, `_mm_abs_pi8`, `_mm_add_epi16`, `_mm_add_epi32` -`_mm_add_epi64`, `_mm_add_epi8`, `_mm_add_pd`, `_mm_add_ps`, `_mm_add_sd` -`_mm_add_si64`, `_mm_adds_epi16`, `_mm_adds_epi8`, `_mm_adds_epu16` -`_mm_adds_epu8`, `_mm_addsub_pd`, `_mm_addsub_ps`, `_mm_and_pd`, `_mm_and_ps` -`_mm_and_si128`, `_mm_andnot_pd`, `_mm_andnot_ps`, `_mm_andnot_si128` -`_mm_avg_epu16`, `_mm_avg_epu8`, `_mm_avg_pu16`, `_mm_avg_pu8` -`_mm_blendv_epi8`, `_mm_blendv_pd`, `_mm_blendv_ps`, `_mm_castpd_ps` -`_mm_castpd_si128`, `_mm_castps_pd`, `_mm_castps_si128`, `_mm_castsi128_pd` -`_mm_castsi128_ps`, `_mm_ceil_pd`, `_mm_ceil_ps`, `_mm_ceil_sd` +`_mm_add_epi64`, `_mm_add_epi8`, `_mm_add_ps`, `_mm_add_si64` +`_mm_adds_epi16`, `_mm_adds_epi8`, `_mm_adds_epu16`, `_mm_adds_epu8` +`_mm_addsub_pd`, `_mm_addsub_ps`, `_mm_and_pd`, `_mm_and_ps`, `_mm_and_si128` +`_mm_andnot_pd`, `_mm_andnot_ps`, `_mm_andnot_si128`, `_mm_avg_epu16` +`_mm_avg_epu8`, `_mm_avg_pu16`, `_mm_avg_pu8`, `_mm_blendv_epi8` +`_mm_blendv_ps`, `_mm_castpd_ps`, `_mm_castpd_si128`, `_mm_castps_pd` +`_mm_castps_si128`, `_mm_castsi128_pd`, `_mm_castsi128_ps`, `_mm_ceil_sd` `_mm_ceil_ss`, `_mm_clflush`, `_mm_clmulepi64_si128`, `_mm_cmpeq_epi16` -`_mm_cmpeq_epi32`, `_mm_cmpeq_epi64`, `_mm_cmpeq_epi8`, `_mm_cmpeq_pd` -`_mm_cmpeq_ps`, `_mm_cmpeq_sd`, `_mm_cmpeq_ss`, `_mm_cmpestra` -`_mm_cmpestrc`, `_mm_cmpestri`, `_mm_cmpestrm`, `_mm_cmpestro` -`_mm_cmpestrs`, `_mm_cmpestrz`, `_mm_cmpge_pd`, `_mm_cmpge_ps` -`_mm_cmpge_sd`, `_mm_cmpge_ss`, `_mm_cmpgt_epi16`, `_mm_cmpgt_epi32` -`_mm_cmpgt_epi64`, `_mm_cmpgt_epi8`, `_mm_cmpgt_pd`, `_mm_cmpgt_ps` -`_mm_cmpgt_sd`, `_mm_cmpgt_ss`, `_mm_cmpistra`, `_mm_cmpistrc` -`_mm_cmpistri`, `_mm_cmpistrm`, `_mm_cmpistro`, `_mm_cmpistrs` -`_mm_cmpistrz`, `_mm_cmple_pd`, `_mm_cmple_ps`, `_mm_cmple_sd` -`_mm_cmple_ss`, `_mm_cmplt_epi16`, `_mm_cmplt_epi32`, `_mm_cmplt_epi8` -`_mm_cmplt_pd`, `_mm_cmplt_ps`, `_mm_cmplt_sd`, `_mm_cmplt_ss` -`_mm_cmpneq_pd`, `_mm_cmpneq_ps`, `_mm_cmpneq_sd`, `_mm_cmpneq_ss` +`_mm_cmpeq_epi32`, `_mm_cmpeq_epi8`, `_mm_cmpeq_ps`, `_mm_cmpeq_sd` +`_mm_cmpeq_ss`, `_mm_cmpestra`, `_mm_cmpestrc`, `_mm_cmpestri` +`_mm_cmpestrm`, `_mm_cmpestro`, `_mm_cmpestrs`, `_mm_cmpestrz` +`_mm_cmpge_ps`, `_mm_cmpge_ss`, `_mm_cmpgt_epi16`, `_mm_cmpgt_epi32` +`_mm_cmpgt_epi8`, `_mm_cmpgt_ps`, `_mm_cmpgt_ss`, `_mm_cmpistra` +`_mm_cmpistrc`, `_mm_cmpistri`, `_mm_cmpistrm`, `_mm_cmpistro` +`_mm_cmpistrs`, `_mm_cmpistrz`, `_mm_cmple_ps`, `_mm_cmple_ss` +`_mm_cmplt_epi16`, `_mm_cmplt_epi32`, `_mm_cmplt_epi8`, `_mm_cmplt_ps` +`_mm_cmplt_ss`, `_mm_cmpneq_ps`, `_mm_cmpneq_sd`, `_mm_cmpneq_ss` `_mm_cmpnge_ps`, `_mm_cmpnge_sd`, `_mm_cmpnge_ss`, `_mm_cmpngt_ps` `_mm_cmpngt_sd`, `_mm_cmpngt_ss`, `_mm_cmpnle_ps`, `_mm_cmpnle_sd` `_mm_cmpnle_ss`, `_mm_cmpnlt_ps`, `_mm_cmpnlt_sd`, `_mm_cmpnlt_ss` -`_mm_cmpord_sd`, `_mm_cmpord_ss`, `_mm_cmpunord_sd`, `_mm_cmpunord_ss` -`_mm_comieq_sd`, `_mm_comieq_ss`, `_mm_comige_sd`, `_mm_comige_ss` -`_mm_comigt_sd`, `_mm_comigt_ss`, `_mm_comile_sd`, `_mm_comile_ss` -`_mm_comilt_sd`, `_mm_comilt_ss`, `_mm_comineq_sd`, `_mm_comineq_ss` -`_mm_cvt_pi2ps`, `_mm_cvt_ps2pi`, `_mm_cvt_si2ss`, `_mm_cvt_ss2si` -`_mm_cvtepi16_epi32`, `_mm_cvtepi16_epi64`, `_mm_cvtepi32_epi64` -`_mm_cvtepi32_pd`, `_mm_cvtepi32_ps`, `_mm_cvtepi8_epi16` +`_mm_cmpord_ss`, `_mm_cmpunord_ss`, `_mm_comieq_ss`, `_mm_comige_ss` +`_mm_comigt_ss`, `_mm_comile_ss`, `_mm_comilt_ss`, `_mm_comineq_sd` +`_mm_comineq_ss`, `_mm_cvt_pi2ps`, `_mm_cvt_ps2pi`, `_mm_cvt_si2ss` +`_mm_cvt_ss2si`, `_mm_cvtepi16_epi32`, `_mm_cvtepi16_epi64` +`_mm_cvtepi32_epi64`, `_mm_cvtepi32_ps`, `_mm_cvtepi8_epi16` `_mm_cvtepi8_epi32`, `_mm_cvtepu16_epi32`, `_mm_cvtepu16_epi64` `_mm_cvtepu32_epi64`, `_mm_cvtepu8_epi16`, `_mm_cvtepu8_epi32` -`_mm_cvtpd_epi32`, `_mm_cvtpd_ps`, `_mm_cvtpi16_ps`, `_mm_cvtpi32_pd` -`_mm_cvtpi32_ps`, `_mm_cvtpi32x2_ps`, `_mm_cvtps_epi32`, `_mm_cvtps_pd` +`_mm_cvtpd_epi32`, `_mm_cvtpi16_ps`, `_mm_cvtpi32_ps`, `_mm_cvtpi32x2_ps` `_mm_cvtps_pi16`, `_mm_cvtps_pi8`, `_mm_cvtpu16_ps`, `_mm_cvtsd_f64` `_mm_cvtsd_si32`, `_mm_cvtsd_si64`, `_mm_cvtsi128_si32`, `_mm_cvtsi128_si64` `_mm_cvtsi32_sd`, `_mm_cvtsi32_si128`, `_mm_cvtsi64_sd`, `_mm_cvtsi64_si128` -`_mm_cvtsi64_ss`, `_mm_cvtss_f32`, `_mm_cvtss_sd`, `_mm_cvtss_si64` -`_mm_cvtt_ps2pi`, `_mm_cvtt_ss2si`, `_mm_cvttpd_epi32`, `_mm_cvttps_epi32` -`_mm_cvttsd_si32`, `_mm_cvttsd_si64`, `_mm_cvttss_si64`, `_mm_div_pd` -`_mm_div_ps`, `_mm_div_ss`, `_mm_empty`, `_mm_floor_pd`, `_mm_floor_ps` +`_mm_cvtsi64_ss`, `_mm_cvtss_f32`, `_mm_cvtss_si64`, `_mm_cvtt_ps2pi` +`_mm_cvtt_ss2si`, `_mm_cvttpd_epi32`, `_mm_cvttps_epi32`, `_mm_cvttsd_si32` +`_mm_cvttsd_si64`, `_mm_cvttss_si64`, `_mm_div_ss`, `_mm_empty` `_mm_floor_sd`, `_mm_floor_ss`, `_mm_free`, `_mm_lfence`, `_mm_load1_pd` -`_mm_load1_ps`, `_mm_load_pd`, `_mm_load_ps`, `_mm_load_sd`, `_mm_load_si128` -`_mm_load_ss`, `_mm_loadh_pd`, `_mm_loadh_pi`, `_mm_loadl_epi64` -`_mm_loadl_pd`, `_mm_loadl_pi`, `_mm_loadr_pd`, `_mm_loadu_pd` -`_mm_loadu_ps`, `_mm_loadu_si128`, `_mm_loadu_si16`, `_mm_loadu_si32` -`_mm_loadu_si64`, `_mm_max_epi16`, `_mm_max_epi32`, `_mm_max_epi8` -`_mm_max_epu16`, `_mm_max_epu32`, `_mm_max_epu8`, `_mm_max_pi16` -`_mm_max_pu8`, `_mm_max_sd`, `_mm_max_ss`, `_mm_mfence`, `_mm_min_epi16` -`_mm_min_epi32`, `_mm_min_epi8`, `_mm_min_epu16`, `_mm_min_epu32` -`_mm_min_epu8`, `_mm_min_pi16`, `_mm_min_pu8`, `_mm_min_sd`, `_mm_min_ss` +`_mm_load1_ps`, `_mm_load_ps`, `_mm_load_si128`, `_mm_load_ss` +`_mm_loadh_pd`, `_mm_loadh_pi`, `_mm_loadl_epi64`, `_mm_loadl_pd` +`_mm_loadl_pi`, `_mm_loadu_pd`, `_mm_loadu_ps`, `_mm_loadu_si128` +`_mm_loadu_si16`, `_mm_loadu_si32`, `_mm_loadu_si64`, `_mm_max_epi16` +`_mm_max_epi32`, `_mm_max_epi8`, `_mm_max_epu16`, `_mm_max_epu32` +`_mm_max_epu8`, `_mm_max_pi16`, `_mm_max_pu8`, `_mm_max_ss`, `_mm_mfence` +`_mm_min_epi16`, `_mm_min_epi32`, `_mm_min_epi8`, `_mm_min_epu16` +`_mm_min_epu32`, `_mm_min_epu8`, `_mm_min_pi16`, `_mm_min_pu8`, `_mm_min_ss` `_mm_monitor`, `_mm_move_epi64`, `_mm_move_sd`, `_mm_move_ss` -`_mm_movedup_pd`, `_mm_movehdup_ps`, `_mm_movehl_ps`, `_mm_moveldup_ps` -`_mm_movelh_ps`, `_mm_movepi64_pi64`, `_mm_movpi64_epi64`, `_mm_mul_epi32` -`_mm_mul_epu32`, `_mm_mul_pd`, `_mm_mul_ps`, `_mm_mul_sd`, `_mm_mul_ss` -`_mm_mul_su32`, `_mm_mulhi_epu16`, `_mm_mulhi_pu16`, `_mm_mulhrs_epi16` -`_mm_mulhrs_pi16`, `_mm_mullo_epi16`, `_mm_mullo_epi32`, `_mm_mwait` -`_mm_or_pd`, `_mm_or_ps`, `_mm_or_si128`, `_mm_packs_epi16` -`_mm_packs_epi32`, `_mm_packus_epi16`, `_mm_packus_epi32`, `_mm_pause` -`_mm_popcnt_u32`, `_mm_popcnt_u64`, `_mm_prefetch`, `_mm_rcp_ss` -`_mm_round_pd`, `_mm_round_ps`, `_mm_round_sd`, `_mm_round_ss` -`_mm_rsqrt_ss`, `_mm_sad_epu8`, `_mm_set1_epi16`, `_mm_set1_epi32` -`_mm_set1_epi64`, `_mm_set1_epi64x`, `_mm_set1_epi8`, `_mm_set1_pd` -`_mm_set1_ps`, `_mm_set_epi64`, `_mm_set_epi64x`, `_mm_set_ps1`, `_mm_set_sd` -`_mm_set_ss`, `_mm_setcsr`, `_mm_setr_epi64`, `_mm_setr_pd`, `_mm_setzero_pd` -`_mm_setzero_ps`, `_mm_setzero_si128`, `_mm_sfence`, `_mm_shuffle_epi_0101` +`_mm_movehl_ps`, `_mm_movelh_ps`, `_mm_movepi64_pi64`, `_mm_movpi64_epi64` +`_mm_mul_epi32`, `_mm_mul_epu32`, `_mm_mul_ps`, `_mm_mul_sd`, `_mm_mul_ss` +`_mm_mul_su32`, `_mm_mulhi_pu16`, `_mm_mulhrs_epi16`, `_mm_mulhrs_pi16` +`_mm_mullo_epi16`, `_mm_mullo_epi32`, `_mm_mwait`, `_mm_or_pd`, `_mm_or_ps` +`_mm_or_si128`, `_mm_packs_epi16`, `_mm_packs_epi32`, `_mm_packus_epi16` +`_mm_packus_epi32`, `_mm_pause`, `_mm_prefetch`, `_mm_rcp_ss`, `_mm_round_sd` +`_mm_round_ss`, `_mm_rsqrt_ss`, `_mm_sad_epu8`, `_mm_set1_epi16` +`_mm_set1_epi32`, `_mm_set1_epi64`, `_mm_set1_epi64x`, `_mm_set1_epi8` +`_mm_set1_pd`, `_mm_set1_ps`, `_mm_set_epi64`, `_mm_set_epi64x` +`_mm_set_ps1`, `_mm_set_sd`, `_mm_set_ss`, `_mm_setcsr`, `_mm_setr_epi64` +`_mm_setr_pd`, `_mm_setzero_pd`, `_mm_setzero_ps`, `_mm_setzero_si128` +`_mm_sfence`, `_mm_shuffle_epi32`, `_mm_shuffle_epi_0101` `_mm_shuffle_epi_0122`, `_mm_shuffle_epi_0321`, `_mm_shuffle_epi_1001` `_mm_shuffle_epi_1010`, `_mm_shuffle_epi_1032`, `_mm_shuffle_epi_2103` `_mm_shuffle_epi_2211`, `_mm_shuffle_epi_2301`, `_mm_shuffle_epi_3332` -`_mm_shuffle_ps_0011`, `_mm_shuffle_ps_0022`, `_mm_shuffle_ps_0101` -`_mm_shuffle_ps_0321`, `_mm_shuffle_ps_1001`, `_mm_shuffle_ps_1010` -`_mm_shuffle_ps_1032`, `_mm_shuffle_ps_1133`, `_mm_shuffle_ps_2103` -`_mm_shuffle_ps_2200`, `_mm_shuffle_ps_2301`, `_mm_shuffle_ps_3202` +`_mm_shuffle_ps`, `_mm_shuffle_ps_0011`, `_mm_shuffle_ps_0022` +`_mm_shuffle_ps_0101`, `_mm_shuffle_ps_0321`, `_mm_shuffle_ps_1001` +`_mm_shuffle_ps_1010`, `_mm_shuffle_ps_1032`, `_mm_shuffle_ps_1133` +`_mm_shuffle_ps_2103`, `_mm_shuffle_ps_2200`, `_mm_shuffle_ps_2301` `_mm_shuffle_ps_3210`, `_mm_slli_epi16`, `_mm_slli_epi32`, `_mm_slli_epi64` -`_mm_sqrt_pd`, `_mm_sqrt_sd`, `_mm_sqrt_ss`, `_mm_store_pd`, `_mm_store_pd1` +`_mm_sqrt_sd`, `_mm_sqrt_ss`, `_mm_srli_epi16`, `_mm_srli_epi32` +`_mm_srli_epi64`, `_mm_srli_si128`, `_mm_store_pd`, `_mm_store_pd1` `_mm_store_ps`, `_mm_store_sd`, `_mm_store_si128`, `_mm_store_ss` `_mm_storeh_pd`, `_mm_storeh_pi`, `_mm_storel_epi64`, `_mm_storel_pd` `_mm_storel_pi`, `_mm_storer_pd`, `_mm_storeu_pd`, `_mm_storeu_ps` `_mm_storeu_si128`, `_mm_storeu_si16`, `_mm_storeu_si32`, `_mm_storeu_si64` `_mm_stream_load_si128`, `_mm_stream_pd`, `_mm_stream_pi`, `_mm_stream_ps` `_mm_stream_si128`, `_mm_stream_si32`, `_mm_stream_si64`, `_mm_sub_epi16` -`_mm_sub_epi32`, `_mm_sub_epi64`, `_mm_sub_epi8`, `_mm_sub_pd`, `_mm_sub_ps` -`_mm_sub_sd`, `_mm_sub_si64`, `_mm_sub_ss`, `_mm_subs_epi16`, `_mm_subs_epi8` +`_mm_sub_epi32`, `_mm_sub_epi64`, `_mm_sub_epi8`, `_mm_sub_ps`, `_mm_sub_sd` +`_mm_sub_si64`, `_mm_sub_ss`, `_mm_subs_epi16`, `_mm_subs_epi8` `_mm_subs_epu16`, `_mm_subs_epu8`, `_mm_test_all_ones`, `_mm_undefined_pd` -`_mm_undefined_ps`, `_mm_undefined_si128`, `_mm_unpackhi_epi16` -`_mm_unpackhi_epi32`, `_mm_unpackhi_epi64`, `_mm_unpackhi_epi8` -`_mm_unpackhi_pd`, `_mm_unpackhi_ps`, `_mm_unpacklo_epi16` -`_mm_unpacklo_epi32`, `_mm_unpacklo_epi64`, `_mm_unpacklo_epi8` -`_mm_unpacklo_pd`, `_mm_unpacklo_ps`, `_mm_xor_pd`, `_mm_xor_ps` -`_mm_xor_si128` +`_mm_undefined_ps`, `_mm_undefined_si128`, `_mm_unpackhi_epi64` +`_mm_unpackhi_pd`, `_mm_unpacklo_epi64`, `_mm_unpacklo_pd`, `_mm_xor_pd` +`_mm_xor_ps`, `_mm_xor_si128` #### Tier 2 (73 intrinsics) -`_mm_add_ss`, `_mm_cmpnge_pd`, `_mm_cmpngt_pd`, `_mm_cmpnle_pd` -`_mm_cmpnlt_pd`, `_mm_cmpord_pd`, `_mm_cmpord_ps`, `_mm_cmpunord_pd` -`_mm_cmpunord_ps`, `_mm_cvtepi8_epi64`, `_mm_cvtepu8_epi64`, `_mm_cvtpi8_ps` -`_mm_cvtpu8_ps`, `_mm_cvtsd_ss`, `_mm_div_sd`, `_mm_hadd_epi16` -`_mm_hadd_epi32`, `_mm_hadd_pd`, `_mm_hadd_pi16`, `_mm_hadd_pi32` -`_mm_hadd_ps`, `_mm_hadds_epi16`, `_mm_hadds_pi16`, `_mm_hsub_epi16` -`_mm_hsub_epi32`, `_mm_hsub_pd`, `_mm_hsub_pi16`, `_mm_hsub_pi32` -`_mm_hsub_ps`, `_mm_hsubs_epi16`, `_mm_hsubs_pi16`, `_mm_loadr_ps` -`_mm_madd_epi16`, `_mm_maskmove_si64`, `_mm_maskmoveu_si128`, `_mm_max_pd` -`_mm_max_ps`, `_mm_min_pd`, `_mm_min_ps`, `_mm_movemask_pd` -`_mm_mulhi_epi16`, `_mm_set_epi16`, `_mm_set_epi32`, `_mm_set_epi8` -`_mm_set_pd`, `_mm_set_ps`, `_mm_setr_epi16`, `_mm_setr_epi32` -`_mm_setr_epi8`, `_mm_setr_ps`, `_mm_shuffle_ps_2001`, `_mm_shuffle_ps_2010` -`_mm_shuffle_ps_2032`, `_mm_sign_epi16`, `_mm_sign_epi32`, `_mm_sign_epi8` -`_mm_sign_pi16`, `_mm_sign_pi32`, `_mm_sign_pi8`, `_mm_sll_epi16` -`_mm_sll_epi32`, `_mm_sll_epi64`, `_mm_sra_epi16`, `_mm_sra_epi32` +`_mm_add_ss`, `_mm_alignr_epi8`, `_mm_blend_epi16`, `_mm_blend_pd` +`_mm_blend_ps`, `_mm_blendv_pd`, `_mm_ceil_pd`, `_mm_cmpeq_epi64` +`_mm_cmpgt_epi64`, `_mm_cmpord_ps`, `_mm_cmpunord_ps`, `_mm_comieq_sd` +`_mm_comige_sd`, `_mm_comigt_sd`, `_mm_comile_sd`, `_mm_comilt_sd` +`_mm_cvtepi32_pd`, `_mm_cvtepi8_epi64`, `_mm_cvtepu8_epi64`, `_mm_cvtpd_ps` +`_mm_cvtpi32_pd`, `_mm_cvtpi8_ps`, `_mm_cvtps_pd`, `_mm_cvtpu8_ps` +`_mm_cvtss_sd`, `_mm_div_sd`, `_mm_floor_pd`, `_mm_hadd_epi16` +`_mm_hadd_epi32`, `_mm_hadd_pi16`, `_mm_hadd_pi32`, `_mm_hadd_ps` +`_mm_loadr_pd`, `_mm_loadr_ps`, `_mm_madd_epi16`, `_mm_maskmove_si64` +`_mm_maskmoveu_si128`, `_mm_max_ps`, `_mm_min_ps`, `_mm_movedup_pd` +`_mm_movemask_pd`, `_mm_mulhi_epi16`, `_mm_mulhi_epu16`, `_mm_set_epi16` +`_mm_set_epi32`, `_mm_set_epi8`, `_mm_set_pd`, `_mm_set_ps`, `_mm_setr_epi16` +`_mm_setr_epi32`, `_mm_setr_epi8`, `_mm_setr_ps`, `_mm_shuffle_pi16` +`_mm_shuffle_ps_2001`, `_mm_shuffle_ps_2010`, `_mm_shuffle_ps_2032` +`_mm_shuffle_ps_3202`, `_mm_sll_epi16`, `_mm_sll_epi32`, `_mm_sll_epi64` +`_mm_slli_si128`, `_mm_sqrt_pd`, `_mm_sra_epi16`, `_mm_sra_epi32` `_mm_srai_epi16`, `_mm_srl_epi16`, `_mm_srl_epi32`, `_mm_srl_epi64` `_mm_store_ps1`, `_mm_storer_ps`, `_mm_test_all_zeros`, `_mm_testc_si128` `_mm_testz_si128` -#### Tier 3 (14 intrinsics) - -`_mm_aeskeygenassist_si128`, `_mm_crc32_u16`, `_mm_crc32_u32` -`_mm_crc32_u64`, `_mm_crc32_u8`, `_mm_cvtpd_pi32`, `_mm_maddubs_epi16` -`_mm_maddubs_pi16`, `_mm_movemask_epi8`, `_mm_movemask_pi8` -`_mm_movemask_ps`, `_mm_rcp_ps`, `_mm_sad_pu8`, `_mm_test_mix_ones_zeros` - -#### Tier 4 (14 intrinsics) - -`_mm_aesdec_si128`, `_mm_aesdeclast_si128`, `_mm_aesenc_si128` -`_mm_aesenclast_si128`, `_mm_aesimc_si128`, `_mm_cvttpd_pi32`, `_mm_dp_pd` -`_mm_dp_ps`, `_mm_minpos_epu16`, `_mm_mpsadbw_epu8`, `_mm_rsqrt_ps` -`_mm_shuffle_epi8`, `_mm_shuffle_pi8`, `_mm_sqrt_ps` +#### Tier 3 (46 intrinsics) + +`_mm_alignr_pi8`, `_mm_ceil_ps`, `_mm_crc32_u16`, `_mm_crc32_u32` +`_mm_crc32_u64`, `_mm_crc32_u8`, `_mm_cvtpd_pi32`, `_mm_cvtsd_ss` +`_mm_div_ps`, `_mm_floor_ps`, `_mm_hadd_pd`, `_mm_hadds_epi16` +`_mm_hadds_pi16`, `_mm_hsub_epi16`, `_mm_hsub_epi32`, `_mm_hsub_pd` +`_mm_hsub_pi16`, `_mm_hsub_pi32`, `_mm_hsub_ps`, `_mm_hsubs_epi16` +`_mm_hsubs_pi16`, `_mm_maddubs_pi16`, `_mm_movehdup_ps`, `_mm_moveldup_ps` +`_mm_popcnt_u32`, `_mm_popcnt_u64`, `_mm_rcp_ps`, `_mm_sad_pu8` +`_mm_shufflehi_epi16_function`, `_mm_shufflelo_epi16_function` +`_mm_sign_epi16`, `_mm_sign_epi32`, `_mm_sign_epi8`, `_mm_sign_pi16` +`_mm_sign_pi32`, `_mm_sign_pi8`, `_mm_srai_epi32`, `_mm_test_mix_ones_zeros` +`_mm_unpackhi_epi16`, `_mm_unpackhi_epi32`, `_mm_unpackhi_epi8` +`_mm_unpackhi_ps`, `_mm_unpacklo_epi16`, `_mm_unpacklo_epi32` +`_mm_unpacklo_epi8`, `_mm_unpacklo_ps` + +#### Tier 4 (52 intrinsics) + +`_mm_add_pd`, `_mm_add_sd`, `_mm_aesdec_si128`, `_mm_aesdeclast_si128` +`_mm_aesenc_si128`, `_mm_aesenclast_si128`, `_mm_aesimc_si128` +`_mm_aeskeygenassist_si128`, `_mm_cmpeq_pd`, `_mm_cmpge_pd`, `_mm_cmpge_sd` +`_mm_cmpgt_pd`, `_mm_cmpgt_sd`, `_mm_cmple_pd`, `_mm_cmple_sd` +`_mm_cmplt_pd`, `_mm_cmplt_sd`, `_mm_cmpneq_pd`, `_mm_cmpnge_pd` +`_mm_cmpngt_pd`, `_mm_cmpnle_pd`, `_mm_cmpnlt_pd`, `_mm_cmpord_pd` +`_mm_cmpord_sd`, `_mm_cmpunord_pd`, `_mm_cmpunord_sd`, `_mm_cvtps_epi32` +`_mm_cvttpd_pi32`, `_mm_div_pd`, `_mm_dp_pd`, `_mm_dp_ps`, `_mm_insert_ps` +`_mm_load_pd`, `_mm_load_sd`, `_mm_maddubs_epi16`, `_mm_max_pd`, `_mm_max_sd` +`_mm_min_pd`, `_mm_min_sd`, `_mm_minpos_epu16`, `_mm_movemask_epi8` +`_mm_movemask_pi8`, `_mm_movemask_ps`, `_mm_mpsadbw_epu8`, `_mm_mul_pd` +`_mm_round_pd`, `_mm_round_ps`, `_mm_rsqrt_ps`, `_mm_shuffle_epi8` +`_mm_shuffle_pi8`, `_mm_sqrt_ps`, `_mm_sub_pd`
diff --git a/sse2neon.h b/sse2neon.h index 16ef2f83..0cd2daca 100644 --- a/sse2neon.h +++ b/sse2neon.h @@ -5774,33 +5774,31 @@ FORCE_INLINE int _mm_movemask_epi8(__m128i a) return vaddv_u8(vget_low_u8(positioned)) | (vaddv_u8(vget_high_u8(positioned)) << 8); #else - // ARMv7: Shift-right-accumulate (no vaddv). - // - // Step 1: Extract MSB of each byte +#if defined(BENCH_ORIGINAL_MOVEMASK_EPI8) + // ARMv7: Shift-right-accumulate (baseline) uint8x16_t msbs = vshrq_n_u8(input, 7); uint64x2_t bits = vreinterpretq_u64_u8(msbs); - - // Step 2: Parallel bit collection via shift-right-accumulate - // - // Initial (8 bytes shown): - // byte: [ 0 ][ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][ 6 ][ 7 ] - // value: [ 01 ][ 00 ][ 01 ][ 01 ][ 00 ][ 01 ][ 00 ][ 01 ] - // - // vsra(..., 7): add original + (original >> 7) - // byte 1 gets: orig[1] + orig[0] = b1|b0 in bits [1:0] - // byte 3 gets: orig[3] + orig[2] = b3|b2 in bits [1:0] - // ... - // Result: pairs combined into odd bytes - // - // vsra(..., 14): combine pairs -> 4 bits in bytes 3,7 - // vsra(..., 28): combine all -> 8 bits in byte 7 (actually byte 0) bits = vsraq_n_u64(bits, bits, 7); bits = vsraq_n_u64(bits, bits, 14); bits = vsraq_n_u64(bits, bits, 28); - - // Step 3: Extract packed result from byte 0 of each half uint8x16_t output = vreinterpretq_u8_u64(bits); return vgetq_lane_u8(output, 0) | (vgetq_lane_u8(output, 8) << 8); +#else + // ARMv7: Pairwise add + // + // Step 1: Extract MSB of each byte as 0x00 or 0xFF + int8x16_t mask = vshrq_n_s8(vreinterpretq_s8_u8(input), 7); + // Step 2: Apply powers of 2 (1, 2, 4, 8, 16, 32, 64, 128) + static const uint8_t w[16] = {1, 2, 4, 8, 16, 32, 64, 128, + 1, 2, 4, 8, 16, 32, 64, 128}; + uint8x16_t weighted = vandq_u8(vreinterpretq_u8_s8(mask), vld1q_u8(w)); + // Step 3: Pairwise add to accumulate the bits + uint8x8_t p = vpadd_u8(vget_low_u8(weighted), vget_high_u8(weighted)); + p = vpadd_u8(p, p); + p = vpadd_u8(p, p); + // Step 4: Extract the 16-bit mask + return vget_lane_u16(vreinterpret_u16_u8(p), 0); +#endif #endif } diff --git a/tests/bench_movemask.cpp b/tests/bench_movemask.cpp new file mode 100644 index 00000000..52440a68 --- /dev/null +++ b/tests/bench_movemask.cpp @@ -0,0 +1,237 @@ +/** + * Benchmark for _mm_movemask_epi8 and related movemask intrinsics. + * + * Measures three dimensions: + * 1. Throughput: independent calls (pipeline utilization) + * 2. Latency: dependent chain (true instruction latency) + * 3. In-context: memchr-like string search (realistic usage) + * + * Build and run: + * make bench-movemask # native x86 + * make bench-movemask CROSS_COMPILE=arm-linux-gnueabihf- # ARMv7 + QEMU + * make bench-movemask CROSS_COMPILE=aarch64-linux-gnu- # AArch64 + QEMU + * + * Inspired by the methodology in PR #704: + * https://github.com/DLTcollab/sse2neon/pull/704 + */ + +#if defined(__aarch64__) || defined(_M_ARM64) || defined(__arm__) +#include "sse2neon.h" +#else +#include +#include +#endif + +#include +#include +#include +#include + +/* ------------------------------------------------------------------ */ +/* Helpers */ +/* ------------------------------------------------------------------ */ + +/* Simple xorshift32 PRNG for reproducible random data. */ +static uint32_t xorshift32(uint32_t *state) +{ + uint32_t x = *state; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + return *state = x; +} + +/* Monotonic clock in nanoseconds. */ +static uint64_t now_ns() +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t) ts.tv_sec * 1000000000ULL + (uint64_t) ts.tv_nsec; +} + +/* Prevent dead-code elimination. */ +static volatile int sink; + +/* ------------------------------------------------------------------ */ +/* Throughput: independent calls */ +/* ------------------------------------------------------------------ */ + +static double bench_throughput(const __m128i *data, + int n_data, + int64_t iters) +{ + uint64_t acc = 0; + uint64_t t0 = now_ns(); + for (int64_t i = 0; i < iters; i++) { + acc += (uint64_t) _mm_movemask_epi8(data[i & (n_data - 1)]); + } + uint64_t t1 = now_ns(); + sink = (int) acc; + return (double) (t1 - t0) / (double) iters; +} + +/* ------------------------------------------------------------------ */ +/* Latency: dependent chain */ +/* ------------------------------------------------------------------ */ + +static double bench_latency(int64_t iters) +{ + __m128i vec = _mm_set1_epi8((char) 0xA5); + uint64_t t0 = now_ns(); + for (int64_t i = 0; i < iters; i++) { + int mask = _mm_movemask_epi8(vec); + /* Feed the result back as input to create a true data dependency. */ + vec = _mm_set1_epi8((char) (mask & 0xFF)); + } + uint64_t t1 = now_ns(); + sink = _mm_movemask_epi8(vec); + return (double) (t1 - t0) / (double) iters; +} + +/* ------------------------------------------------------------------ */ +/* In-context: memchr-like byte search using cmpeq + movemask */ +/* ------------------------------------------------------------------ */ + +static double bench_memchr_like(const uint8_t *haystack, + int len, + uint8_t needle, + int64_t iters) +{ + __m128i target = _mm_set1_epi8((char) needle); + int found_count = 0; + uint64_t t0 = now_ns(); + for (int64_t iter = 0; iter < iters; iter++) { + for (int i = 0; i <= len - 16; i += 16) { + __m128i chunk = + _mm_loadu_si128((const __m128i *) (haystack + i)); + __m128i cmp = _mm_cmpeq_epi8(chunk, target); + int mask = _mm_movemask_epi8(cmp); + if (mask) { + found_count++; + break; + } + } + } + uint64_t t1 = now_ns(); + sink = found_count; + return (double) (t1 - t0) / (double) iters; +} + +/* ------------------------------------------------------------------ */ +/* Warm-up: run a few iterations to stabilise caches & branch predictors */ +/* ------------------------------------------------------------------ */ + +static void warmup(__m128i *data, int n_data) +{ + uint64_t acc = 0; + for (int i = 0; i < n_data * 4; i++) + acc += (uint64_t) _mm_movemask_epi8(data[i & (n_data - 1)]); + sink = (int) acc; +} + +/* ------------------------------------------------------------------ */ +/* Main */ +/* ------------------------------------------------------------------ */ + +int main() +{ + const int64_t ITERS = 10000000; /* 10 M iterations */ + const int N_DATA = 1024; /* must be power of 2 */ + + /* --- Prepare input patterns --- */ + __m128i data_zero[N_DATA]; + __m128i data_all[N_DATA]; + __m128i data_alt[N_DATA]; + __m128i data_cmpresult[N_DATA]; + __m128i data_rand[N_DATA]; + + uint32_t rng = 42; + volatile char dyn_zero = 0; // Prevent constant-folding at -O3 + + for (int i = 0; i < N_DATA; i++) { + uint32_t r[4]; + for (int j = 0; j < 4; j++) + r[j] = xorshift32(&rng); + data_rand[i] = _mm_loadu_si128((const __m128i *) r); + + /* Derive other arrays using dyn_zero so they aren't compile-time constants */ + data_zero[i] = _mm_set1_epi8((char) dyn_zero); + data_all[i] = _mm_set1_epi8((char) (dyn_zero | 0xFF)); + + char v80 = (char)(dyn_zero | 0x80); + data_alt[i] = _mm_set_epi8( + (char) dyn_zero, v80, (char) dyn_zero, v80, + (char) dyn_zero, v80, (char) dyn_zero, v80, + (char) dyn_zero, v80, (char) dyn_zero, v80, + (char) dyn_zero, v80, (char) dyn_zero, v80); + + char vFF = (char)(dyn_zero | 0xFF); + data_cmpresult[i] = _mm_set_epi8( + vFF, (char) dyn_zero, vFF, (char) dyn_zero, + vFF, vFF, (char) dyn_zero, (char) dyn_zero, + vFF, vFF, vFF, + (char) dyn_zero, (char) dyn_zero, (char) dyn_zero, vFF, vFF); + } + + /* --- Warm up --- */ + warmup(data_rand, N_DATA); + + /* --- Header --- */ + printf("=== _mm_movemask_epi8 Benchmark ===\n"); +#if defined(__aarch64__) || defined(_M_ARM64) + printf("Architecture: AArch64\n"); +#elif defined(__arm__) + printf("Architecture: ARMv7-A\n"); +#elif defined(__x86_64__) + printf("Architecture: x86_64\n"); +#elif defined(__i386__) + printf("Architecture: x86 (32-bit)\n"); +#else + printf("Architecture: unknown\n"); +#endif + printf("Iterations: %lld\n\n", (long long) ITERS); + + /* --- Throughput --- */ + printf("--- Throughput (ns/op, independent calls) ---\n"); + printf(" All-zero: %8.2f\n", + bench_throughput(data_zero, N_DATA, ITERS)); + printf(" All-ones: %8.2f\n", + bench_throughput(data_all, N_DATA, ITERS)); + printf(" Alternating: %8.2f\n", + bench_throughput(data_alt, N_DATA, ITERS)); + printf(" Cmp-result: %8.2f\n", + bench_throughput(data_cmpresult, N_DATA, ITERS)); + printf(" Random: %8.2f\n", + bench_throughput(data_rand, N_DATA, ITERS)); + + /* --- Latency --- */ + printf("\n--- Latency (ns/op, dependent chain) ---\n"); + printf(" Dep-chain: %8.2f\n", bench_latency(ITERS)); + + /* --- In-context --- */ + printf("\n--- In-context: memchr-like search (ns/search) ---\n"); + uint8_t haystack[4096]; + memset(haystack, 0x42, sizeof(haystack)); + + /* Needle at the midpoint */ + haystack[2048] = 0xAA; + printf(" Found@2048: %8.2f\n", + bench_memchr_like(haystack, (int) sizeof(haystack), 0xAA, + ITERS / 10)); + + /* Needle not present */ + haystack[2048] = 0x42; + printf(" Not-found: %8.2f\n", + bench_memchr_like(haystack, (int) sizeof(haystack), 0xAA, + ITERS / 10)); + + /* Needle at the very start */ + haystack[0] = 0xAA; + printf(" Found@0: %8.2f\n", + bench_memchr_like(haystack, (int) sizeof(haystack), 0xAA, + ITERS / 10)); + haystack[0] = 0x42; + + printf("\nDone.\n"); + return 0; +}