Предотвращение накладных расходов локального хранилища потоков (делая масштабируемым ffmpeg YADIF) - PullRequest
1 голос
/ 31 июля 2011

Я пытаюсь создать небольшой "хак" для ffmpeg, который разрешает параллельное выполнение фильтра yadif.

Я думаю, что нашел решение, однако его может быть только один параллельный экземпляр.Это связано с тем, что «scalable_yadif_context» является локальным для функции «scalable_yadif_filter_line1», которая заменяет исходную функцию «filter_line» yadif.Я мог бы сделать поток «scalable_yadif_context» локальным, однако, поскольку эта функция вызывается часто, это может привести к довольно высоким издержкам.

Есть идеи, как решить эту проблему?

// We need the context description in order to access the original filter_line function. Just redefine it here and hope that it is not changed inside of libavfilter.
typedef struct {
    int mode;
    int parity;
    int frame_pending;
    int auto_enable;
    AVFilterBufferRef *cur;
    AVFilterBufferRef *next;
    AVFilterBufferRef *prev;
    AVFilterBufferRef *out;
    void (*filter_line)(uint8_t *dst,
                        uint8_t *prev, uint8_t *cur, uint8_t *next,
                        int w, int prefs, int mrefs, int parity, int mode);
    const AVPixFmtDescriptor *csp;
} YADIFContext;

struct scalable_yadif_context
{
    std::vector<std::function<void()>> calls;
    int end_prefs;

    scalable_yadif_context() : end_prefs(std::numeric_limits<int>::max()){}
};

void (*org_yadif_filter_line)(uint8_t *dst, uint8_t *prev, uint8_t *cur, uint8_t *next, int w, int prefs, int mrefs, int parity, int mode) = 0;

void scalable_yadif_filter_line(scalable_yadif_context& ctx, uint8_t *dst, uint8_t *prev, uint8_t *cur, uint8_t *next, int w, int prefs, int mrefs, int parity, int mode)
{
    if(ctx.end_prefs == std::numeric_limits<int>::max())
        ctx.end_prefs = -prefs;  // Last call to filter_line will have negative pref

    ctx.calls.push_back([=]
    {
        org_yadif_filter_line(dst, prev, cur, next, w, prefs, mrefs, parity, mode);
    });    

    if(prefs == ctx.end_prefs)
    {       
        tbb::parallel_for(tbb::blocked_range<size_t>(0, ctx.calls.size()), [=](const tbb::blocked_range<size_t>& r)
        {
            for(auto n = r.begin(); n != r.end(); ++n)
                ctx.calls[n]();
        });
        ctx.calls.clear();
        ctx.end_prefs = std::numeric_limits<int>::max();
    }
}

void scalable_yadif_filter_line1(uint8_t *dst, uint8_t *prev, uint8_t *cur, uint8_t *next, int w, int prefs, int mrefs, int parity, int mode)
{
     // local to the current function, making this thread local would incur heavy overhead.
    static scalable_yadif_context ctx;
    scalable_yadif_filter_line(ctx, dst, prev, cur, next, w, prefs, mrefs, parity, mode);
}

void make_scalable_yadif(AVFilterContext* ctx)
{
    YADIFContext* yadif = (YADIFContext*)ctx->priv;

    // Data race should not be problem since we are always writing the same value
    org_yadif_filter_line = yadif->filter_line;

    // hmm, will only work for one concurrent instance... 
    // I need a unique "scalable_yadif_filter_line1" for each call...
    yadif->filter_line = scalable_yadif_filter_line1; 
}

IВы создали крайне уродливое решение, которое работает до 18 одновременных экземпляров.

#define RENAME(a) f ## a

#define ff(x) \
void RENAME(x)(uint8_t *dst, uint8_t *prev, uint8_t *cur, uint8_t *next, int w, int prefs, int mrefs, int parity, int mode) \
{\
    static scalable_yadif_context ctx;\
    scalable_yadif_filter_line(ctx, dst, prev, cur, next, w, prefs, mrefs, parity, mode);\
}

ff(0); ff(1); ff(2); ff(3); ff(4); ff(5); ff(6); ff(7); ff(8); ff(9); ff(10); ff(11); ff(12); ff(13); ff(14); ff(15); ff(16); ff(17);

void (*fs[])(uint8_t *dst, uint8_t *prev, uint8_t *cur, uint8_t *next, int w, int prefs, int mrefs, int parity, int mode) = 

{f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17};

namespace caspar {

void init()
{
    for(int n = 0; n < 18; ++n)
        tags.push(n);
}

int make_scalable_yadif(AVFilterContext* ctx)
{
    static boost::once_flag flag = BOOST_ONCE_INIT;
    boost::call_once(&init, flag);

    YADIFContext* yadif = (YADIFContext*)ctx->priv;
    org_yadif_filter_line = yadif->filter_line;

    int tag;
    if(!tags.try_pop(tag))
    {
        LOG(warning) << "Not enough scalable-yadif instances. Running non-scalable";
        return -1;
    }

    yadif->filter_line = fs[tag];
    return tag;
}

void release_scalable_yadif(int tag)
{
    if(tag != -1)
        tags.push(tag);
}

1 Ответ

1 голос
/ 31 июля 2011

Почему бы просто не передать буфер для потока в функцию scalable_yadif_filter_line1? Это может потребовать небольшой реорганизации, но это гораздо лучше, чем использование статики или локальных потоков (в конце концов, что произойдет с локальным буфером вашего потока, если поток продолжит делать что-то еще?)

Если вы не можете передать буфер функции (из-за фиксированного ffmpeg API), TLS, вероятно, ваш единственный выбор. Накладные расходы не так плохи, как вы думаете, но все же не так хороши. Я настоятельно рекомендую изучить изменение ffmpeg для добавления параметра контекста.

...