From 63ddb1abfa52d227f1244ec06475bd5f93a4ce8e Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Thu, 30 Jul 2026 02:47:28 +0000 Subject: [PATCH 27/38] avcodec/ppc: bench-driven per-CPU tuning of AltiVec registrations Gate individual AltiVec registrations on _ARCH_PWR4 (predefined by GCC for -mcpu=970/G5, absent for -mcpu=7450/G4) so one source tree auto-tunes per MacPorts CPU variant, and drop the few kernels that measured slower than C on both CPUs. Registration-level excludes only: all kernel code stays compiled (av_unused where a build shape loses its only reference), so any single decision here is a one-line revert. Data: checkasm --bench on real hardware, C-vs-AltiVec tick ratios. - G5: 970 2.3 GHz, full patched tree (2026-07-28). - G4: 7447A 1.42 GHz Mac mini (2026-07-30) - an unpatched stock build, so it only provides numbers for pre-existing upstream kernels. Every G5-only gate below therefore leaves the G4 side unchanged; a patched G4 bench can revisit. Disabled on G5 only (#ifndef _ARCH_PWR4, measured loss vs C on 970): - h264pred: pred16x16_horizontal 0.43x, pred8x8_horizontal 0.19x, pred8x8 dc 0.56x / left_dc 0.53x / top_dc 0.63x - h264dsp: h_loop_filter_chroma 0.66x, h_loop_filter_chroma_intra 0.60x, h_loop_filter_luma_intra 0.65x (all transpose-bound; the vertical counterparts win 2.2-6.9x and stay) - h264dsp 4x4 idct family: idct_add 0.53x, idct_dc_add 0.52x, idct_add16 0.74x (the 7447A measured a 1.27x WIN on idct_add16, so the family stays on G4); idct_add8 has no checkasm bench but is the same two kernels in a chroma loop, so it goes with idct_add16 - hpeldsp put_pixels8 (put_pixels_tab[1][0]) 0.42x, and its h264qpel twin put_h264_qpel8_mc00 (same function underneath) - vp8dsp put_vp8_pixels16 0.46x (measured with the byte-precise unaligned store of e34797f; an aligned-dst fast path could win it back on both CPUs - future work) - vp9dsp: hor ipred 16x16 0.39x / 32x32 0.67x; 4-wide bilin MC 0.70-0.89x (avg_bilin_4h measured 1.06x at noise-level tick counts and is gated with its family) Disabled on G4 only (#ifdef _ARCH_PWR4): - hpeldsp avg_pixels8 (avg_pixels_tab[1][0]): 0.80x on 7447A vs 2.46x win on 970, and its h264qpel twin avg_h264_qpel8_mc00 Disabled everywhere (measured loss on both CPUs): - h264dsp h_loop_filter_luma: 0.87x G4 / 0.90x G5 - h264dsp idct_add16intra: 0.52x G4 / 0.78x G5 Left registered despite sub-1.0 bench entries (timer noise or wash): pred8x8 vertical 0.91x / dc_128 0.90x (0.7-tick entries), h264 chroma_mc4 0.96-0.97x, vp9 put4 0.83x (0.3 ticks), vp9 avg8 0.92x, sw_scale hscale (mixed across filter sizes, shared registration). The MacPorts +G4/+G5 variants pass -mcpu=G4 / -mcpu=970 -mno-powerpc64 respectively; -mcpu=970 predefines _ARCH_PWR4 (verified with GCC 14), so no Portfile changes are needed. Builds without -mcpu (including the qemu checkasm harness) get the G4 registration set. Validated: cross-PPC qemu harness (-cpu 7400), full checkasm suite 577/577 on seeds 42 and 777 (was 581; -4 = the two disabled-everywhere kernels plus avg_pixels8/avg qpel8 mc00, which are G5-only and absent from the harness's G4-shape build). All six files also compile warning-free with -D_ARCH_PWR4 simulating the G5 shape. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016PpTqZd4mMkQFMFRSf7k8c --- libavcodec/ppc/h264dsp.c | 49 +++++++++++++++++++------------- libavcodec/ppc/h264pred_init.c | 6 ++++ libavcodec/ppc/h264qpel.c | 19 +++++++++++-- libavcodec/ppc/hpeldsp_altivec.c | 4 +++ libavcodec/ppc/vp8dsp_altivec.c | 5 +++- libavcodec/ppc/vp9dsp_altivec.c | 24 ++++++++++------ 6 files changed, 77 insertions(+), 30 deletions(-) diff --git a/libavcodec/ppc/h264dsp.c b/libavcodec/ppc/h264dsp.c index 5cee7f9..6181599 100644 --- a/libavcodec/ppc/h264dsp.c +++ b/libavcodec/ppc/h264dsp.c @@ -340,9 +340,9 @@ static void h264_idct8_dc_add_altivec(uint8_t *dst, int16_t *block, int stride) h264_idct_dc_add_internal(dst, block, stride, 8); } -static void h264_idct_add16_altivec(uint8_t *dst, const int *block_offset, - int16_t *block, int stride, - const uint8_t nnzc[5 * 8]) +static av_unused void h264_idct_add16_altivec(uint8_t *dst, const int *block_offset, + int16_t *block, int stride, + const uint8_t nnzc[5 * 8]) { int i; for(i=0; i<16; i++){ @@ -354,8 +354,8 @@ static void h264_idct_add16_altivec(uint8_t *dst, const int *block_offset, } } -static void h264_idct_add16intra_altivec(uint8_t *dst, const int *block_offset, - int16_t *block, int stride, +static av_unused void h264_idct_add16intra_altivec(uint8_t *dst, const int *block_offset, + int16_t *block, int stride, const uint8_t nnzc[5 * 8]) { int i; @@ -379,9 +379,9 @@ static void h264_idct8_add4_altivec(uint8_t *dst, const int *block_offset, } } -static void h264_idct_add8_altivec(uint8_t **dest, const int *block_offset, - int16_t *block, int stride, - const uint8_t nnzc[15 * 8]) +static av_unused void h264_idct_add8_altivec(uint8_t **dest, const int *block_offset, + int16_t *block, int stride, + const uint8_t nnzc[15 * 8]) { int i, j; for (j = 1; j < 3; j++) { @@ -719,7 +719,7 @@ static void h264_v_loop_filter_luma_altivec(uint8_t *pix, ptrdiff_t stride, int } } -static void h264_h_loop_filter_luma_altivec(uint8_t *pix, ptrdiff_t stride, int alpha, int beta, int8_t *tc0) { +static av_unused void h264_h_loop_filter_luma_altivec(uint8_t *pix, ptrdiff_t stride, int alpha, int beta, int8_t *tc0) { register vec_u8 line0, line1, line2, line3, line4, line5; if ((tc0[0] & tc0[1] & tc0[2] & tc0[3]) < 0) @@ -861,8 +861,8 @@ static void h264_v_loop_filter_luma_intra_altivec(uint8_t *pix, ptrdiff_t stride vec_st(q2, 2*stride, pix); } -static void h264_h_loop_filter_luma_intra_altivec(uint8_t *pix, ptrdiff_t stride, - int alpha, int beta) +static av_unused void h264_h_loop_filter_luma_intra_altivec(uint8_t *pix, ptrdiff_t stride, + int alpha, int beta) { /* p3 p2 p1 p0 q0 q1 q2 q3 = columns of the 16 rows starting at pix-4 */ register vec_u8 p3, p2, p1, p0, q0, q1, q2, q3; @@ -956,8 +956,8 @@ static void h264_v_loop_filter_chroma_altivec(uint8_t *pix, ptrdiff_t stride, col23 = (vec_u8)vec_mergel((vec_u32)u0, (vec_u32)u1); \ } -static void h264_h_loop_filter_chroma_altivec(uint8_t *pix, ptrdiff_t stride, - int alpha, int beta, int8_t *tc0) +static av_unused void h264_h_loop_filter_chroma_altivec(uint8_t *pix, ptrdiff_t stride, + int alpha, int beta, int8_t *tc0) { register vec_u8 col01, col23; register vec_u8 p1, p0, q0, q1; @@ -1050,8 +1050,8 @@ static void h264_v_loop_filter_chroma_intra_altivec(uint8_t *pix, ptrdiff_t stri store8_altivec(pix, q0); } -static void h264_h_loop_filter_chroma_intra_altivec(uint8_t *pix, ptrdiff_t stride, - int alpha, int beta) +static av_unused void h264_h_loop_filter_chroma_intra_altivec(uint8_t *pix, ptrdiff_t stride, + int alpha, int beta) { register vec_u8 col01, col23; register vec_u8 p1, p0, q0, q1; @@ -1216,24 +1216,35 @@ av_cold void ff_h264dsp_init_ppc(H264DSPContext *c, const int bit_depth, return; if (bit_depth == 8) { +#ifndef _ARCH_PWR4 /* 970: idct_add 0.53x, idct_dc_add 0.52x, idct_add16 + * 0.74x vs C (7447A: add16 1.27x win, add/dc_add wash); + * idct_add8 is unbenched but is the same two kernels in + * a chroma loop, so it goes with idct_add16 */ c->idct_add = h264_idct_add_altivec; if (chroma_format_idc <= 1) c->idct_add8 = h264_idct_add8_altivec; - c->idct_add16 = h264_idct_add16_altivec; - c->idct_add16intra = h264_idct_add16intra_altivec; + c->idct_add16 = h264_idct_add16_altivec; c->idct_dc_add = h264_idct_dc_add_altivec; +#endif + /* idct_add16intra: not registered, loses to C on both 7447A (0.52x) + * and 970 (0.78x) */ c->idct8_dc_add = h264_idct8_dc_add_altivec; c->idct8_add = h264_idct8_add_altivec; c->idct8_add4 = h264_idct8_add4_altivec; c->v_loop_filter_luma = h264_v_loop_filter_luma_altivec; - c->h_loop_filter_luma = h264_h_loop_filter_luma_altivec; + /* h_loop_filter_luma: not registered, loses to C on both 7447A + * (0.87x) and 970 (0.90x) — transpose-bound */ c->v_loop_filter_luma_intra = h264_v_loop_filter_luma_intra_altivec; +#ifndef _ARCH_PWR4 /* 970: 0.65x vs C */ c->h_loop_filter_luma_intra = h264_h_loop_filter_luma_intra_altivec; +#endif if (chroma_format_idc <= 1) { c->v_loop_filter_chroma = h264_v_loop_filter_chroma_altivec; - c->h_loop_filter_chroma = h264_h_loop_filter_chroma_altivec; c->v_loop_filter_chroma_intra = h264_v_loop_filter_chroma_intra_altivec; +#ifndef _ARCH_PWR4 /* 970: h 0.66x, h intra 0.60x vs C */ + c->h_loop_filter_chroma = h264_h_loop_filter_chroma_altivec; c->h_loop_filter_chroma_intra = h264_h_loop_filter_chroma_intra_altivec; +#endif } c->weight_pixels_tab[0] = weight_h264_pixels16_altivec; diff --git a/libavcodec/ppc/h264pred_init.c b/libavcodec/ppc/h264pred_init.c index b7cd012..68e1972 100644 --- a/libavcodec/ppc/h264pred_init.c +++ b/libavcodec/ppc/h264pred_init.c @@ -40,7 +40,9 @@ av_cold void ff_h264_pred_init_ppc(H264PredContext *h, int codec_id, return; h->pred16x16[VERT_PRED8x8 ] = ff_pred16x16_vertical_altivec; +#ifndef _ARCH_PWR4 /* 970: 0.43x vs C */ h->pred16x16[HOR_PRED8x8 ] = ff_pred16x16_horizontal_altivec; +#endif h->pred16x16[DC_PRED8x8 ] = ff_pred16x16_dc_altivec; h->pred16x16[LEFT_DC_PRED8x8] = ff_pred16x16_left_dc_altivec; h->pred16x16[TOP_DC_PRED8x8 ] = ff_pred16x16_top_dc_altivec; @@ -48,15 +50,19 @@ av_cold void ff_h264_pred_init_ppc(H264PredContext *h, int codec_id, if (chroma_format_idc <= 1) { h->pred8x8[VERT_PRED8x8 ] = ff_pred8x8_vertical_altivec; +#ifndef _ARCH_PWR4 /* 970: 0.19x vs C */ h->pred8x8[HOR_PRED8x8 ] = ff_pred8x8_horizontal_altivec; +#endif h->pred8x8[DC_128_PRED8x8] = ff_pred8x8_128_dc_altivec; +#ifndef _ARCH_PWR4 /* 970: dc 0.56x, left_dc 0.53x, top_dc 0.63x vs C */ if (codec_id != AV_CODEC_ID_RV40 && codec_id != AV_CODEC_ID_VP7 && codec_id != AV_CODEC_ID_VP8) { h->pred8x8[DC_PRED8x8 ] = ff_pred8x8_dc_altivec; h->pred8x8[LEFT_DC_PRED8x8] = ff_pred8x8_left_dc_altivec; h->pred8x8[TOP_DC_PRED8x8 ] = ff_pred8x8_top_dc_altivec; } +#endif } #endif } diff --git a/libavcodec/ppc/h264qpel.c b/libavcodec/ppc/h264qpel.c index 67ed36b..96aa335 100644 --- a/libavcodec/ppc/h264qpel.c +++ b/libavcodec/ppc/h264qpel.c @@ -370,8 +370,23 @@ av_cold void ff_h264qpel_init_ppc(H264QpelContext *c, int bit_depth) dspfunc(put_h264_qpel, 0, 16); dspfunc(avg_h264_qpel, 0, 16); #if HAVE_BIGENDIAN - dspfunc(put_h264_qpel, 1, 8); - dspfunc(avg_h264_qpel, 1, 8); + { + /* 8-wide mc00 is a plain 8x8 copy/avg (ff_put/avg_pixels8_altivec + * underneath): put loses to C on the 970 (0.42x), avg loses on + * the 7447A (0.80x, 970 2.46x win) — keep the C entry there. */ +#ifdef _ARCH_PWR4 + qpel_mc_func put8_mc00_c = c->put_h264_qpel_pixels_tab[1][0]; +#else + qpel_mc_func avg8_mc00_c = c->avg_h264_qpel_pixels_tab[1][0]; +#endif + dspfunc(put_h264_qpel, 1, 8); + dspfunc(avg_h264_qpel, 1, 8); +#ifdef _ARCH_PWR4 + c->put_h264_qpel_pixels_tab[1][0] = put8_mc00_c; +#else + c->avg_h264_qpel_pixels_tab[1][0] = avg8_mc00_c; +#endif + } #endif #undef dspfunc } diff --git a/libavcodec/ppc/hpeldsp_altivec.c b/libavcodec/ppc/hpeldsp_altivec.c index def7972..d0d7d4f 100644 --- a/libavcodec/ppc/hpeldsp_altivec.c +++ b/libavcodec/ppc/hpeldsp_altivec.c @@ -397,11 +397,15 @@ av_cold void ff_hpeldsp_init_ppc(HpelDSPContext *c, int flags) return; c->avg_pixels_tab[0][0] = ff_avg_pixels16_altivec; +#ifdef _ARCH_PWR4 /* 7447A: 0.80x vs C; 970: 2.46x win */ c->avg_pixels_tab[1][0] = ff_avg_pixels8_altivec; +#endif c->avg_pixels_tab[1][3] = avg_pixels8_xy2_altivec; c->put_pixels_tab[0][0] = ff_put_pixels16_altivec; +#ifndef _ARCH_PWR4 /* 970: 0.42x vs C (scalar 8x8 copy wins) */ c->put_pixels_tab[1][0] = ff_put_pixels8_altivec; +#endif c->put_pixels_tab[1][3] = put_pixels8_xy2_altivec; c->put_pixels_tab[0][3] = put_pixels16_xy2_altivec; diff --git a/libavcodec/ppc/vp8dsp_altivec.c b/libavcodec/ppc/vp8dsp_altivec.c index acea9fc..f25742e 100644 --- a/libavcodec/ppc/vp8dsp_altivec.c +++ b/libavcodec/ppc/vp8dsp_altivec.c @@ -326,7 +326,7 @@ EPEL_HV(4, 4,6) EPEL_HV(4, 6,4) EPEL_HV(4, 4,4) -static void put_vp8_pixels16_altivec(uint8_t *dst, ptrdiff_t dstride, const uint8_t *src, ptrdiff_t sstride, int h, int mx, int my) +static av_unused void put_vp8_pixels16_altivec(uint8_t *dst, ptrdiff_t dstride, const uint8_t *src, ptrdiff_t sstride, int h, int mx, int my) { int i; register ptrdiff_t dstride2 = dstride << 1, sstride2 = sstride << 1; @@ -363,7 +363,10 @@ av_cold void ff_vp78dsp_init_ppc(VP8DSPContext *c) if (!PPC_ALTIVEC(av_get_cpu_flags())) return; +#ifndef _ARCH_PWR4 /* 970: 0.46x vs C (with the byte-precise unaligned store; + * an aligned-dst fast path could win it back — future work) */ c->put_vp8_epel_pixels_tab[0][0][0] = put_vp8_pixels16_altivec; +#endif c->put_vp8_epel_pixels_tab[0][0][2] = put_vp8_epel16_h6_altivec; c->put_vp8_epel_pixels_tab[0][2][0] = put_vp8_epel16_v6_altivec; c->put_vp8_epel_pixels_tab[0][2][2] = put_vp8_epel16_h6v6_altivec; diff --git a/libavcodec/ppc/vp9dsp_altivec.c b/libavcodec/ppc/vp9dsp_altivec.c index 70b58e8..bfdb485 100644 --- a/libavcodec/ppc/vp9dsp_altivec.c +++ b/libavcodec/ppc/vp9dsp_altivec.c @@ -653,8 +653,8 @@ static void vert_32x32_altivec(uint8_t *dst, ptrdiff_t stride, } } -static void hor_16x16_altivec(uint8_t *dst, ptrdiff_t stride, - const uint8_t *left, const uint8_t *top) +static av_unused void hor_16x16_altivec(uint8_t *dst, ptrdiff_t stride, + const uint8_t *left, const uint8_t *top) { int y; @@ -664,8 +664,8 @@ static void hor_16x16_altivec(uint8_t *dst, ptrdiff_t stride, } } -static void hor_32x32_altivec(uint8_t *dst, ptrdiff_t stride, - const uint8_t *left, const uint8_t *top) +static av_unused void hor_32x32_altivec(uint8_t *dst, ptrdiff_t stride, + const uint8_t *left, const uint8_t *top) { int y; @@ -1731,8 +1731,9 @@ VP9_8TAP_HV_FUNCS( 4, avg, 1) #undef VP9_8TAP_HV #undef VP9_8TAP_HV_FUNCS +/* av_unused: the 4-wide instances go unreferenced on _ARCH_PWR4 builds */ #define VP9_BILIN_V(sz, type, avg) \ -static void type##_bilin_##sz##v_altivec(uint8_t *dst, \ +static av_unused void type##_bilin_##sz##v_altivec(uint8_t *dst, \ ptrdiff_t dst_stride, \ const uint8_t *src, \ ptrdiff_t src_stride, \ @@ -1742,7 +1743,7 @@ static void type##_bilin_##sz##v_altivec(uint8_t *dst, \ } #define VP9_BILIN_H(sz, type, avg) \ -static void type##_bilin_##sz##h_altivec(uint8_t *dst, \ +static av_unused void type##_bilin_##sz##h_altivec(uint8_t *dst, \ ptrdiff_t dst_stride, \ const uint8_t *src, \ ptrdiff_t src_stride, \ @@ -1752,7 +1753,7 @@ static void type##_bilin_##sz##h_altivec(uint8_t *dst, \ } #define VP9_BILIN_HV(sz, type, avg) \ -static void type##_bilin_##sz##hv_altivec(uint8_t *dst, \ +static av_unused void type##_bilin_##sz##hv_altivec(uint8_t *dst, \ ptrdiff_t dst_stride, \ const uint8_t *src, \ ptrdiff_t src_stride, \ @@ -1848,7 +1849,6 @@ static av_cold void vp9dsp_intra_init_ppc(VP9DSPContext *dsp) { #define init_intra(tx, sz) \ dsp->intra_pred[tx][VERT_PRED] = vert_##sz##_altivec; \ - dsp->intra_pred[tx][HOR_PRED] = hor_##sz##_altivec; \ dsp->intra_pred[tx][DC_PRED] = dc_##sz##_altivec; \ dsp->intra_pred[tx][LEFT_DC_PRED] = dc_left_##sz##_altivec; \ dsp->intra_pred[tx][TOP_DC_PRED] = dc_top_##sz##_altivec; \ @@ -1859,6 +1859,11 @@ static av_cold void vp9dsp_intra_init_ppc(VP9DSPContext *dsp) init_intra(TX_16X16, 16x16); init_intra(TX_32X32, 32x32); +#ifndef _ARCH_PWR4 /* 970: hor 16x16 0.39x, 32x32 0.67x vs C */ + dsp->intra_pred[TX_16X16][HOR_PRED] = hor_16x16_altivec; + dsp->intra_pred[TX_32X32][HOR_PRED] = hor_32x32_altivec; +#endif + #undef init_intra } @@ -1892,7 +1897,10 @@ static av_cold void vp9dsp_mc_init_ppc(VP9DSPContext *dsp) init_bilin_avg(1, 32); init_bilin_avg(2, 16); init_bilin_avg(3, 8); +#ifndef _ARCH_PWR4 /* 970: 4-wide bilin 0.70-0.89x vs C (avg_4h 1.06x is + * within timer noise and goes with its family) */ init_bilin_avg(4, 4); +#endif } /* Two-pass 4x4 add for a given (row-pass, col-pass) transform pair. -- 2.43.0