[FFmpeg-devel] [PATCH] avfilter/avf_showcqt: cqt_calc optimization on x86

Muhammad Faiz mfcc64 at gmail.com
Wed Jun 8 11:16:32 CEST 2016


On Tue, Jun 7, 2016 at 4:18 PM, Muhammad Faiz <mfcc64 at gmail.com> wrote:
> On Tue, Jun 7, 2016 at 10:36 AM, James Almer <jamrial at gmail.com> wrote:
>> On 6/4/2016 4:36 AM, Muhammad Faiz wrote:
>>> benchmark on x86_64
>>> cqt_time:
>>> plain = 3.292 s
>>> SSE   = 1.640 s
>>> SSE3  = 1.631 s
>>> AVX   = 1.395 s
>>> FMA3  = 1.271 s
>>> FMA4  = not available
>>
>> Try using the START_TIMER and STOP_TIMER macros to wrap the s->cqt_calc
>> call in libavfilter/avf_showcqt.c
>> It will potentially give more accurate results than the current
>> UPDATE_TIME(s->cqt_time) check.
>>
> OK, but probably I will check it privately (not sending patch)
>
>>>
>>> untested on x86_32
>>
>> Do you have a sample command to test this? As Michael said FATE doesn't
>> cover showcqt.
>>
>
> I check it using psnr filter (avg psnr above 90dB means OK)
>
> #!/bin/bash
> # example usage: ./psnr-check audio.mp3 yuv420p "-cpuflags -fma3-fma4-avx-sse3"
>
> mkfifo in0.y4m
> mkfifo in1.y4m
>
> # this is new ffmpeg
> build_path=$HOME/Documents/sources/ffmpeg/ffmpeg-build
> LD_LIBRARY_PATH=$build_path/libavcodec:$build_path/libavdevice:$build_path/libavfilter
> LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$build_path/libavformat:$build_path/libavutil
> LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$build_path/libpostproc:$build_path/libswresample
> LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$build_path/libswscale
>
> export LD_LIBRARY_PATH
>
> $build_path/ffmpeg $3 -i "$1" -filter_complex "showcqt, format=$2,
> format=yuv444p|yuv422p|yuv420p" -f yuv4mpegpipe -y in0.y4m >/dev/null
> 2>&1 </dev/null &
>
> # this is old ffmpeg
> unset LD_LIBRARY_PATH
> ffmpeg $3 -i "$1" -filter_complex "showcqt, format=$2,
> format=yuv444p|yuv422p|yuv420p" -f yuv4mpegpipe -y in1.y4m >/dev/null
> 2>&1 </dev/null &
>
> ffmpeg -i $dir/in0.y4m -i $dir/in1.y4m -filter_complex "psnr=f=-" -f
> null -y /dev/null
>
>
>>>
>>> Signed-off-by: Muhammad Faiz <mfcc64 at gmail.com>
>>> ---
>>>  libavfilter/avf_showcqt.c          |   7 ++
>>>  libavfilter/avf_showcqt.h          |   3 +
>>>  libavfilter/x86/Makefile           |   2 +
>>>  libavfilter/x86/avf_showcqt.asm    | 206 +++++++++++++++++++++++++++++++++++++
>>>  libavfilter/x86/avf_showcqt_init.c |  63 ++++++++++++
>>>  5 files changed, 281 insertions(+)
>>>  create mode 100644 libavfilter/x86/avf_showcqt.asm
>>>  create mode 100644 libavfilter/x86/avf_showcqt_init.c
>>>
>>> diff --git a/libavfilter/avf_showcqt.c b/libavfilter/avf_showcqt.c
>>> index b88c83c..62d5b09 100644
>>> --- a/libavfilter/avf_showcqt.c
>>> +++ b/libavfilter/avf_showcqt.c
>>> @@ -320,6 +320,9 @@ static int init_cqt(ShowCQTContext *s)
>>>              w *= sign * (1.0 / s->fft_len);
>>>              s->coeffs[m].val[x - s->coeffs[m].start] = w;
>>>          }
>>> +
>>> +        if (s->permute_coeffs)
>>> +            s->permute_coeffs(s->coeffs[m].val, s->coeffs[m].len);
>>>      }
>>>
>>>      av_expr_free(expr);
>>> @@ -1230,6 +1233,7 @@ static int config_output(AVFilterLink *outlink)
>>>
>>>      s->cqt_align = 1;
>>>      s->cqt_calc = cqt_calc;
>>> +    s->permute_coeffs = NULL;
>>>      s->draw_sono = draw_sono;
>>>      if (s->format == AV_PIX_FMT_RGB24) {
>>>          s->draw_bar = draw_bar_rgb;
>>> @@ -1241,6 +1245,9 @@ static int config_output(AVFilterLink *outlink)
>>>          s->update_sono = update_sono_yuv;
>>>      }
>>>
>>> +    if (ARCH_X86)
>>> +        ff_showcqt_init_x86(s);
>>> +
>>>      if ((ret = init_cqt(s)) < 0)
>>>          return ret;
>>>
>>> diff --git a/libavfilter/avf_showcqt.h b/libavfilter/avf_showcqt.h
>>> index b945f49..588830f 100644
>>> --- a/libavfilter/avf_showcqt.h
>>> +++ b/libavfilter/avf_showcqt.h
>>> @@ -74,6 +74,7 @@ typedef struct {
>>>      /* callback */
>>>      void                (*cqt_calc)(FFTComplex *dst, const FFTComplex *src, const Coeffs *coeffs,
>>>                                      int len, int fft_len);
>>> +    void                (*permute_coeffs)(float *v, int len);
>>>      void                (*draw_bar)(AVFrame *out, const float *h, const float *rcp_h,
>>>                                      const ColorFloat *c, int bar_h);
>>>      void                (*draw_axis)(AVFrame *out, AVFrame *axis, const ColorFloat *c, int off);
>>> @@ -112,4 +113,6 @@ typedef struct {
>>>      int                 axis;
>>>  } ShowCQTContext;
>>>
>>> +void ff_showcqt_init_x86(ShowCQTContext *s);
>>> +
>>>  #endif
>>> diff --git a/libavfilter/x86/Makefile b/libavfilter/x86/Makefile
>>> index 4486b79..b6195f8 100644
>>> --- a/libavfilter/x86/Makefile
>>> +++ b/libavfilter/x86/Makefile
>>> @@ -13,6 +13,7 @@ OBJS-$(CONFIG_PP7_FILTER)                    += x86/vf_pp7_init.o
>>>  OBJS-$(CONFIG_PSNR_FILTER)                   += x86/vf_psnr_init.o
>>>  OBJS-$(CONFIG_PULLUP_FILTER)                 += x86/vf_pullup_init.o
>>>  OBJS-$(CONFIG_REMOVEGRAIN_FILTER)            += x86/vf_removegrain_init.o
>>> +OBJS-$(CONFIG_SHOWCQT_FILTER)                += x86/avf_showcqt_init.o
>>>  OBJS-$(CONFIG_SPP_FILTER)                    += x86/vf_spp.o
>>>  OBJS-$(CONFIG_SSIM_FILTER)                   += x86/vf_ssim_init.o
>>>  OBJS-$(CONFIG_STEREO3D_FILTER)               += x86/vf_stereo3d_init.o
>>> @@ -37,6 +38,7 @@ YASM-OBJS-$(CONFIG_PULLUP_FILTER)            += x86/vf_pullup.o
>>>  ifdef CONFIG_GPL
>>>  YASM-OBJS-$(CONFIG_REMOVEGRAIN_FILTER)       += x86/vf_removegrain.o
>>>  endif
>>> +YASM-OBJS-$(CONFIG_SHOWCQT_FILTER)           += x86/avf_showcqt.o
>>>  YASM-OBJS-$(CONFIG_SSIM_FILTER)              += x86/vf_ssim.o
>>>  YASM-OBJS-$(CONFIG_STEREO3D_FILTER)          += x86/vf_stereo3d.o
>>>  YASM-OBJS-$(CONFIG_TBLEND_FILTER)            += x86/vf_blend.o
>>> diff --git a/libavfilter/x86/avf_showcqt.asm b/libavfilter/x86/avf_showcqt.asm
>>> new file mode 100644
>>> index 0000000..ba30786
>>> --- /dev/null
>>> +++ b/libavfilter/x86/avf_showcqt.asm
>>> @@ -0,0 +1,206 @@
>>> +;*****************************************************************************
>>> +;* x86-optimized functions for showcqt filter
>>> +;*
>>> +;* Copyright (C) 2016 Muhammad Faiz <mfcc64 at gmail.com>
>>> +;*
>>> +;* This file is part of FFmpeg.
>>> +;*
>>> +;* FFmpeg is free software; you can redistribute it and/or
>>> +;* modify it under the terms of the GNU Lesser General Public
>>> +;* License as published by the Free Software Foundation; either
>>> +;* version 2.1 of the License, or (at your option) any later version.
>>> +;*
>>> +;* FFmpeg is distributed in the hope that it will be useful,
>>> +;* but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> +;* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>> +;* Lesser General Public License for more details.
>>> +;*
>>> +;* You should have received a copy of the GNU Lesser General Public
>>> +;* License along with FFmpeg; if not, write to the Free Software
>>> +;* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>>> +;******************************************************************************
>>> +
>>> +%include "libavutil/x86/x86util.asm"
>>> +
>>> +%if ARCH_X86_64
>>> +%define pointer resq
>>> +%else
>>> +%define pointer resd
>>> +%endif
>>> +
>>> +struc Coeffs
>>> +    .val:   pointer 1
>>> +    .start: resd 1
>>> +    .len:   resd 1
>>> +    .sizeof:
>>> +endstruc
>>> +
>>> +%macro EMULATE_HADDPS 3 ; dst, src, tmp
>>> +%if cpuflag(sse3)
>>> +    haddps  %1, %2
>>> +%else
>>> +    movaps  %3, %1
>>> +    shufps  %1, %2, q2020
>>> +    shufps  %3, %2, q3131
>>> +    addps   %1, %3
>>
>> This is great. Much better and more efficient than other attempts to
>> emulate haddps scattered across the codebase.
>> It also makes me wonder if haddps, a ~5 cycles latency instruction is
>> really better than the combination of a mostly free mov, two 1 cycle
>> shuffles and one 3 cycle add to justify extra functions with it as the
>> only difference, at least in cases where there are no register
>> constrains.
>>
>> Your benchmarks above suggest it is although barely, so I'm curious
>> about what the timer.h macros will show.
>>
> Even probably haddps is slower than shufps+addps on some machine
> But, there is haddps and I use it.
> Updated using SSE3_FAST
>
>>> +%endif
>>> +%endmacro ; EMULATE_HADDPS
>>> +
>>> +%macro EMULATE_FMADDPS 5 ; dst, src1, src2, src3, tmp
>>> +%if cpuflag(fma3) || cpuflag(fma4)
>>> +    fmaddps %1, %2, %3, %4
>>> +%else
>>> +    mulps   %5, %2, %3
>>> +    addps   %1, %4, %5
>>> +%endif
>>> +%endmacro ; EMULATE_FMADDPS
>>> +
>>> +%macro CQT_CALC 9
>>> +; %1 = a_re, %2 = a_im, %3 = b_re, %4 = b_im
>>> +; %5 = m_re, %6 = m_im, %7 = tmp, %8 = coeffval, %9 = coeffsq_offset
>>> +    mov     id, xd
>>> +    add     id, [coeffsq + Coeffs.start + %9]
>>> +    movaps  m%5, [srcq + 8 * iq]
>>> +    movaps  m%7, [srcq + 8 * iq + mmsize]
>>> +    shufps  m%6, m%5, m%7, q3131
>>> +    shufps  m%5, m%5, m%7, q2020
>>> +    sub     id, fft_lend
>>> +    EMULATE_FMADDPS m%2, m%6, m%8, m%2, m%6
>>> +    neg     id
>>
>> Is this supposed to turn a positive value negative? If so then it should
>> be "neg iq", otherwise on x86_64 the high 32 bits of iq used in the
>> effective addresses below would be zero.
>>
> This is the intended behavior (i = fft_len - i) evaluated with int not int64_t
>
>>> +    EMULATE_FMADDPS m%1, m%5, m%8, m%1, m%5
>>> +    movups  m%5, [srcq + 8 * iq - mmsize + 8]
>>> +    movups  m%7, [srcq + 8 * iq - 2*mmsize + 8]
>>> +    %if mmsize == 32
>>> +    vperm2f128 m%5, m%5, m%5, 1
>>> +    vperm2f128 m%7, m%7, m%7, 1
>>> +    %endif
>>> +    shufps  m%6, m%5, m%7, q1313
>>> +    shufps  m%5, m%5, m%7, q0202
>>> +    EMULATE_FMADDPS m%4, m%6, m%8, m%4, m%6
>>> +    EMULATE_FMADDPS m%3, m%5, m%8, m%3, m%5
>>> +%endmacro ; CQT_CALC
>>> +
>>> +%macro CQT_SEPARATE 6 ; a_re, a_im, b_re, b_im, tmp, tmp2
>>> +    addps   m%5, m%4, m%2
>>> +    subps   m%6, m%3, m%1
>>> +    addps   m%1, m%3
>>> +    subps   m%2, m%4
>>> +    EMULATE_HADDPS m%5, m%6, m%3
>>> +    EMULATE_HADDPS m%1, m%2, m%3
>>> +    EMULATE_HADDPS m%1, m%5, m%2
>>> +    %if mmsize == 32
>>> +    vextractf128 xmm%2, m%1, 1
>>> +    addps   xmm%1, xmm%2
>>> +    %endif
>>> +%endmacro ; CQT_SEPARATE
>>> +
>>> +%macro DECLARE_CQT_CALC 0
>>> +; ff_showcqt_cqt_calc_*(dst, src, coeffs, len, fft_len)
>>> +%if ARCH_X86_64
>>> +cglobal showcqt_cqt_calc, 5, 10, 12, dst, src, coeffs, len, fft_len, x, coeffs_val, coeffs_val2, i, coeffs_len
>>> +    align   16
>>> +    .loop_k:
>>> +        mov     xd, [coeffsq + Coeffs.len]
>>> +        xorps   m0, m0
>>> +        movaps  m1, m0
>>> +        movaps  m2, m0
>>> +        mov     coeffs_lend, [coeffsq + Coeffs.len + Coeffs.sizeof]
>>> +        movaps  m3, m0
>>> +        movaps  m8, m0
>>> +        cmp     coeffs_lend, xd
>>> +        movaps  m9, m0
>>> +        movaps  m10, m0
>>> +        movaps  m11, m0
>>> +        cmova   coeffs_lend, xd
>>> +        xor     xd, xd
>>> +        test    coeffs_lend, coeffs_lend
>>> +        jz      .check_loop_b
>>> +        mov     coeffs_valq, [coeffsq + Coeffs.val]
>>> +        mov     coeffs_val2q, [coeffsq + Coeffs.val + Coeffs.sizeof]
>>> +        align   16
>>> +        .loop_ab:
>>> +            movaps  m7, [coeffs_valq + 4 * xq]
>>> +            CQT_CALC 0, 1, 2, 3, 4, 5, 6, 7, 0
>>> +            movaps  m7, [coeffs_val2q + 4 * xq]
>>> +            CQT_CALC 8, 9, 10, 11, 4, 5, 6, 7, Coeffs.sizeof
>>> +            add     xd, mmsize/4
>>> +            cmp     xd, coeffs_lend
>>> +            jb      .loop_ab
>>> +        .check_loop_b:
>>> +        cmp     xd, [coeffsq + Coeffs.len + Coeffs.sizeof]
>>> +        jae     .check_loop_a
>>> +        align   16
>>> +        .loop_b:
>>> +            movaps  m7, [coeffs_val2q + 4 * xq]
>>> +            CQT_CALC 8, 9, 10, 11, 4, 5, 6, 7, Coeffs.sizeof
>>> +            add     xd, mmsize/4
>>> +            cmp     xd, [coeffsq + Coeffs.len + Coeffs.sizeof]
>>> +            jb      .loop_b
>>> +        .loop_end:
>>> +        CQT_SEPARATE 0, 1, 2, 3, 4, 5
>>> +        CQT_SEPARATE 8, 9, 10, 11, 4, 5
>>> +        mulps   xmm0, xmm0
>>> +        mulps   xmm8, xmm8
>>> +        EMULATE_HADDPS xmm0, xmm8, xmm1
>>> +        movaps  [dstq], xmm0
>>> +        sub     lend, 2
>>> +        lea     dstq, [dstq + 16]
>>
>> Use add
>>
>>> +        lea     coeffsq, [coeffsq + 2*Coeffs.sizeof]
>>
>> Same, assuming sizeof is an immediate.
>>
> This is optimization to separate sub and jnz with lea.
> Using add will clobber flag register.
> Also lea does not need rex prefix
>
>>> +        jnz     .loop_k
>>> +        REP_RET
>>> +        align   16
>>> +        .check_loop_a:
>>> +        cmp     xd, [coeffsq + Coeffs.len]
>>> +        jae     .loop_end
>>> +        align   16
>>> +        .loop_a:
>>> +            movaps  m7, [coeffs_valq + 4 * xq]
>>> +            CQT_CALC 0, 1, 2, 3, 4, 5, 6, 7, 0
>>> +            add     xd, mmsize/4
>>> +            cmp     xd, [coeffsq + Coeffs.len]
>>> +            jb      .loop_a
>>> +        jmp     .loop_end
>>> +%else
>>> +cglobal showcqt_cqt_calc, 4, 7, 8, dst, src, coeffs, len, x, coeffs_val, i
>>> +%define fft_lend r4m
>>> +    align   16
>>> +    .loop_k:
>>> +        mov     xd, [coeffsq + Coeffs.len]
>>> +        xorps   m0, m0
>>> +        movaps  m1, m0
>>> +        movaps  m2, m0
>>> +        movaps  m3, m0
>>> +        test    xd, xd
>>> +        jz      .store
>>> +        mov     coeffs_valq, [coeffsq + Coeffs.val]
>>> +        xor     xd, xd
>>> +        align   16
>>> +        .loop_x:
>>> +            movaps  m7, [coeffs_valq + 4 * xq]
>>> +            CQT_CALC 0, 1, 2, 3, 4, 5, 6, 7, 0
>>> +            add     xd, mmsize/4
>>> +            cmp     xd, [coeffsq + Coeffs.len]
>>> +            jb      .loop_x
>>> +        CQT_SEPARATE 0, 1, 2, 3, 4, 5
>>> +        mulps   xmm0, xmm0
>>> +        EMULATE_HADDPS xmm0, xmm0, xmm1
>>> +        .store:
>>> +        movlps  [dstq], xmm0
>>> +        sub     lend, 1
>>> +        lea     dstq, [dstq + 8]
>>> +        lea     coeffsq, [coeffsq + Coeffs.sizeof]
>>
>> Same as above for both of these leas.
>>
> Same answer.
>
>>> +        jnz     .loop_k
>>> +        REP_RET
>>> +%endif ; ARCH_X86_64
>>> +%endmacro ; DECLARE_CQT_CALC
>>> +
>>> +INIT_XMM sse
>>> +DECLARE_CQT_CALC
>>> +INIT_XMM sse3
>>> +DECLARE_CQT_CALC
>>> +INIT_YMM avx
>>> +DECLARE_CQT_CALC
>>> +INIT_YMM fma3
>>> +DECLARE_CQT_CALC
>>> +INIT_YMM fma4
>>
>> All CPUs supporting FMA4 underperform in functions using ymm registers.
>> Make it xmm instead.
>>
> OK, updated
>
>>> +DECLARE_CQT_CALC
>>> diff --git a/libavfilter/x86/avf_showcqt_init.c b/libavfilter/x86/avf_showcqt_init.c
>>> new file mode 100644
>>> index 0000000..664c6ac
>>> --- /dev/null
>>> +++ b/libavfilter/x86/avf_showcqt_init.c
>>> @@ -0,0 +1,63 @@
>>> +/*
>>> + * Copyright (c) 2016 Muhammad Faiz <mfcc64 at gmail.com>
>>> + *
>>> + * This file is part of FFmpeg.
>>> + *
>>> + * FFmpeg is free software; you can redistribute it and/or
>>> + * modify it under the terms of the GNU Lesser General Public
>>> + * License as published by the Free Software Foundation; either
>>> + * version 2.1 of the License, or (at your option) any later version.
>>> + *
>>> + * FFmpeg is distributed in the hope that it will be useful,
>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>> + * Lesser General Public License for more details.
>>> + *
>>> + * You should have received a copy of the GNU Lesser General Public
>>> + * License along with FFmpeg; if not, write to the Free Software
>>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>>> + */
>>> +
>>> +#include "libavutil/attributes.h"
>>> +#include "libavutil/cpu.h"
>>> +#include "libavutil/x86/cpu.h"
>>> +#include "libavfilter/avf_showcqt.h"
>>> +
>>> +#define DECLARE_CQT_CALC(type) \
>>> +void ff_showcqt_cqt_calc_##type(FFTComplex *dst, const FFTComplex *src, \
>>> +                                const Coeffs *coeffs, int len, int fft_len)
>>> +
>>> +DECLARE_CQT_CALC(sse);
>>> +DECLARE_CQT_CALC(sse3);
>>> +DECLARE_CQT_CALC(avx);
>>> +DECLARE_CQT_CALC(fma3);
>>> +DECLARE_CQT_CALC(fma4);
>>> +
>>> +#define permute_coeffs_0 NULL
>>> +
>>> +static void permute_coeffs_01452367(float *v, int len)
>>> +{
>>> +    int k;
>>> +    for (k = 0; k < len; k += 8) {
>>> +        FFSWAP(float, v[k+2], v[k+4]);
>>> +        FFSWAP(float, v[k+3], v[k+5]);
>>> +    }
>>> +}
>>> +
>>> +av_cold void ff_showcqt_init_x86(ShowCQTContext *s)
>>> +{
>>> +    int cpuflags = av_get_cpu_flags();
>>> +
>>> +#define SELECT_CQT_CALC(type, TYPE, align, perm) \
>>> +if (EXTERNAL_##TYPE(cpuflags)) { \
>>> +    s->cqt_calc = ff_showcqt_cqt_calc_##type; \
>>> +    s->cqt_align = align; \
>>> +    s->permute_coeffs = permute_coeffs_##perm; \
>>> +}
>>> +
>>> +    SELECT_CQT_CALC(sse,  SSE,  4, 0);
>>> +    SELECT_CQT_CALC(sse3, SSE3, 4, 0);
>>> +    SELECT_CQT_CALC(avx,  AVX,  8, 01452367);
>>
>> Use AVX_FAST, so this function will not be used on CPUs that set the
>> AV_CPU_FLAG_AVXSLOW flag.
>>
>>> +    SELECT_CQT_CALC(fma3, FMA3, 8, 01452367);
>>
>> Same, use FMA3_FAST. The result will then be the FMA3 version used by
>> Intel CPUs and hopefully AMD Zen, and the FMA4 one by Bulldozer based
>> CPUs.
>>
>>> +    SELECT_CQT_CALC(fma4, FMA4, 8, 01452367);
>>> +}
>>>
>>
>
> OK, also reorder (FMA4 before AVX because AVX/ymm without FMA4 is faster than
> FMA4/xmm)
>
> Modified patch attached
>
> Thank's

applied with modified message

Thank's


More information about the ffmpeg-devel mailing list