x265探索与研究(八):x265中的并行处理机制函数关系分析

简介: <h1 style="text-align:center"><strong>x265<span style="font-family:宋体">探索与研究(八):</span>x265<span style="font-family:宋体">中</span>的并行处理机制函数关系分析</strong></h1> <p> </p> <p>        HEVC<span style="f

x265探索与研究(八):x265的并行处理机制函数关系分析

 

        HEVC的高计算复杂度如果仅仅依赖于单核处理器计算能力的提高,其代价是非常昂贵的,为此,HEVC的设计充分考虑到了并行计算的需求。x265不同于HMHEVC test Model),x265将并行计算发挥地更加淋淋尽致。在main()函数中,为了完成多线程计算,读完24帧输入帧后才开始编码的原因也基于此。

 

        为了理清x265中的并行处理机制,首先给出了如下图的并行处理函数关系图:

 



        经过前面几篇文章的分析,我们知道main()函数主要调用了encoder_open()函数、encoder_headers()函数、encoder_encode()函数与encoder_close()函数。其中:

1encoder_encode()函数调用了encode()函数,而encode()函数中调用了startCompressFrame()函数,在startCompressFrame()函数中,采用m_enable.trigger完成了触发线程的功能;

2encoder_open()函数调用了encoder_create()函数,在encoder_create()函数中等待线程的初始化并进入threadMain()函数中等待线程的触发,线程一旦触发则调用compressFrame函数进行进一步地编码工作。

        这两步的协调工作完美地成就了多线程的实现。为了进一步分析HEVC的并行处理机制,接下来首先分析视频编码的并行处理相关技术,然后依次分析该函数关系图中较为重要的startCompressFrame()函数、encoder_create()函数以及threadMain()函数。

 

1、并行处理技术

 

        并行处理一般指许多指令得以同时进行的处理模式,通常分为两种:功能并行和数据并行。

1)功能并行是指将应用程序划分成互相独立的功能模块,每个模块间可以并行地执行,这种并行方式也称为流水线型并行,它将各个独立的模块划分给不同的运算单元,各个模块之间通过流的方式来进行数据交换和通信,最终再将各个单元串接在一起。功能并行充分利用了时间上的并行性来获得加速的效果,比较适合于硬件实现。功能并行的缺点是很明显的,a、由于分配给不同运算单元的功能模块是不同的,因此很容易产生载荷失衡问题;b、功能并行还需要在不同运算单元间进行数据通信,当数据量较大时,需要花费额外的资源来进行存储;c、另外,功能并行的扩展性较差。

2)数据并行是将数据信息划分为相互独立的部分,每一部分交给不同的运算单元来执行,从而实现并行处理。在这种方式下,不同运算单元上执行的程序是相同的,而且处理的是相互独立的数据信息,因此不需要进行运算单元间的通信;数据并行也具有较好的扩展性,易于软件实现。

 

        HEVC/H.265提供了适用于进行数据并行处理的结构单元,如片和Tile,在不同的片和Tile中,数据信息是相互独立的,这样有利于将其分配给不同的运算单元来处理;对于小于片和Tile的划分单元,HEVC支持波前并行处理(Wavefront Parallel Processing, WPP),这是对于相互具有依赖关系的图像单元进行数据并行处理的方法。在HEVC中,并行处理技术主要包括:基于Tile的并行和波前并行两种。在进行基于Tile的并行时,由于Tile的相互独立性,不需要考虑它们之间的相互依赖关系,而在进行波前并行处理时,数据间的相互依赖关系是必不可少的。

 

2startCompressFrame()函数的分析

 

        startCompressFrame()函数的主要功能就是触发线程,对应的代码分析如下:

 

/*=============================================================*/
/*
 ====== Analysed by: RuiDong Fang 
 ====== Csdn Blog:	 http://blog.csdn.net/frd2009041510 
 ====== Date:		 2016.04.15
 ====== Funtion:	 startCompressFrame()函数,触发线程。
 */
/*=============================================================*/
bool FrameEncoder::startCompressFrame(Frame* curFrame)
{
    m_slicetypeWaitTime = x265_mdate() - m_prevOutputTime;
    m_frame = curFrame;
    m_param = curFrame->m_param;
    m_sliceType = curFrame->m_lowres.sliceType;
    curFrame->m_encData->m_frameEncoderID = m_jpId;
    curFrame->m_encData->m_jobProvider = this;
    curFrame->m_encData->m_slice->m_mref = m_mref;

    if (!m_cuGeoms)
    {
        if (!initializeGeoms())
            return false;
    }

    m_enable.trigger();	//触发线程,下一步将会进入threadMain()函数中
    return true;
}

 

3encoder_create()函数的分析

 

        encoder_create()函数的主要功能是检测线程池、可用的线程数目等等,若线程使用的条件符合则调用threadMain()函数,对应的代码分析如下:

 

void Encoder::create()
{
    if (!primitives.pu[0].sad)
    {
        // this should be an impossible condition when using our public API, and indicates a serious bug.
        x265_log(m_param, X265_LOG_ERROR, "Primitives must be initialized before encoder is created\n");
        abort();
    }

    x265_param* p = m_param;

    int rows = (p->sourceHeight + p->maxCUSize - 1) >> g_log2Size[p->maxCUSize];
    int cols = (p->sourceWidth  + p->maxCUSize - 1) >> g_log2Size[p->maxCUSize];

    // Do not allow WPP if only one row or fewer than 3 columns, it is pointless and unstable
	//对于不符合条件的,不进行WPP
    if (rows == 1 || cols < 3)
    {
        x265_log(p, X265_LOG_WARNING, "Too few rows/columns, --wpp disabled\n");
        p->bEnableWavefront = 0;
    }

    bool allowPools = !p->numaPools || strcmp(p->numaPools, "none");

    // Trim the thread pool if --wpp, --pme, and --pmode are disabled
	//如果--wpp, --pme, and --pmode不使能,清理线程池
    if (!p->bEnableWavefront && !p->bDistributeModeAnalysis && !p->bDistributeMotionEstimation && !p->lookaheadSlices)
        allowPools = false;

	//根据核数检测线程的数目
    if (!p->frameNumThreads)
    {
        // auto-detect frame threads
        int cpuCount = ThreadPool::getCpuCount();
        if (!p->bEnableWavefront)
            p->frameNumThreads = X265_MIN3(cpuCount, (rows + 1) / 2, X265_MAX_FRAME_THREADS);
        else if (cpuCount >= 32)
            p->frameNumThreads = (p->sourceHeight > 2000) ? 8 : 6; // dual-socket 10-core IvyBridge or higher
        else if (cpuCount >= 16)
            p->frameNumThreads = 5; // 8 HT cores, or dual socket
        else if (cpuCount >= 8)
            p->frameNumThreads = 3; // 4 HT cores
        else if (cpuCount >= 4)
            p->frameNumThreads = 2; // Dual or Quad core
        else
            p->frameNumThreads = 1;
    }

    m_numPools = 0;
    if (allowPools)
        m_threadPool = ThreadPool::allocThreadPools(p, m_numPools);

    if (!m_numPools)
    {
        // issue warnings if any of these features were requested
        if (p->bEnableWavefront)
            x265_log(p, X265_LOG_WARNING, "No thread pool allocated, --wpp disabled\n");
        if (p->bDistributeMotionEstimation)
            x265_log(p, X265_LOG_WARNING, "No thread pool allocated, --pme disabled\n");
        if (p->bDistributeModeAnalysis)
            x265_log(p, X265_LOG_WARNING, "No thread pool allocated, --pmode disabled\n");
        if (p->lookaheadSlices)
            x265_log(p, X265_LOG_WARNING, "No thread pool allocated, --lookahead-slices disabled\n");

        // disable all pool features if the thread pool is disabled or unusable.
        p->bEnableWavefront = p->bDistributeModeAnalysis = p->bDistributeMotionEstimation = p->lookaheadSlices = 0;
    }

    if (!p->bEnableWavefront && p->rc.vbvBufferSize)
    {
        x265_log(p, X265_LOG_ERROR, "VBV requires wavefront parallelism\n");
        m_aborted = true;
    }

    char buf[128];
    int len = 0;
    if (p->bEnableWavefront)
        len += sprintf(buf + len, "wpp(%d rows)", rows);
    if (p->bDistributeModeAnalysis)
        len += sprintf(buf + len, "%spmode", len ? "+" : "");
    if (p->bDistributeMotionEstimation)
        len += sprintf(buf + len, "%spme ", len ? "+" : "");
    if (!len)
        strcpy(buf, "none");

    x265_log(p, X265_LOG_INFO, "frame threads / pool features       : %d / %s\n", p->frameNumThreads, buf);

    for (int i = 0; i < m_param->frameNumThreads; i++)
    {
        m_frameEncoder[i] = new FrameEncoder;
        m_frameEncoder[i]->m_nalList.m_annexB = !!m_param->bAnnexB;
    }

    if (m_numPools)
    {
        for (int i = 0; i < m_param->frameNumThreads; i++)
        {
            int pool = i % m_numPools;
            m_frameEncoder[i]->m_pool = &m_threadPool[pool];
            m_frameEncoder[i]->m_jpId = m_threadPool[pool].m_numProviders++;
            m_threadPool[pool].m_jpTable[m_frameEncoder[i]->m_jpId] = m_frameEncoder[i];
        }
        for (int i = 0; i < m_numPools; i++)
            m_threadPool[i].start();
    }
    else
    {
        /* CU stats and noise-reduction buffers are indexed by jpId, so it cannot be left as -1 */
        for (int i = 0; i < m_param->frameNumThreads; i++)
            m_frameEncoder[i]->m_jpId = 0;
    }

    if (!m_scalingList.init())
    {
        x265_log(m_param, X265_LOG_ERROR, "Unable to allocate scaling list arrays\n");
        m_aborted = true;
    }
    else if (!m_param->scalingLists || !strcmp(m_param->scalingLists, "off"))
        m_scalingList.m_bEnabled = false;
    else if (!strcmp(m_param->scalingLists, "default"))
        m_scalingList.setDefaultScalingList();
    else if (m_scalingList.parseScalingList(m_param->scalingLists))
        m_aborted = true;
    m_scalingList.setupQuantMatrices();

    m_lookahead = new Lookahead(m_param, m_threadPool);
    if (m_numPools)
    {
        m_lookahead->m_jpId = m_threadPool[0].m_numProviders++;
        m_threadPool[0].m_jpTable[m_lookahead->m_jpId] = m_lookahead;
    }

    m_dpb = new DPB(m_param);
    m_rateControl = new RateControl(*m_param);

    initVPS(&m_vps);
    initSPS(&m_sps);
    initPPS(&m_pps);

    int numRows = (m_param->sourceHeight + g_maxCUSize - 1) / g_maxCUSize;
    int numCols = (m_param->sourceWidth  + g_maxCUSize - 1) / g_maxCUSize;
    for (int i = 0; i < m_param->frameNumThreads; i++)
    {
        if (!m_frameEncoder[i]->init(this, numRows, numCols))
        {
            x265_log(m_param, X265_LOG_ERROR, "Unable to initialize frame encoder, aborting\n");
            m_aborted = true;
        }
    }

    for (int i = 0; i < m_param->frameNumThreads; i++)
    {
        m_frameEncoder[i]->start();
        m_frameEncoder[i]->m_done.wait(); /* wait for thread to initialize */	//========调用threadMain()
    }

    if (m_param->bEmitHRDSEI)
        m_rateControl->initHRD(m_sps);
    if (!m_rateControl->init(m_sps))
        m_aborted = true;
    if (!m_lookahead->create())
        m_aborted = true;

    if (m_param->analysisMode)
    {
        const char* name = m_param->analysisFileName;
        if (!name)
            name = defaultAnalysisFileName;
        const char* mode = m_param->analysisMode == X265_ANALYSIS_LOAD ? "rb" : "wb";
        m_analysisFile = fopen(name, mode);
        if (!m_analysisFile)
        {
            x265_log(NULL, X265_LOG_ERROR, "Analysis load/save: failed to open file %s\n", name);
            m_aborted = true;
        }
    }

    m_bZeroLatency = !m_param->bframes && !m_param->lookaheadDepth && m_param->frameNumThreads == 1;

    m_aborted |= parseLambdaFile(m_param);

    m_encodeStartTime = x265_mdate();

    m_nalList.m_annexB = !!m_param->bAnnexB;
}

 

4threadMain()函数的分析

 

        threadMain()函数相当于线程函数的main()函数,其主要功能就是在完成线程触发后等待处理,在此处,调用了compressFrame()函数。

 

        对应的代码分析如下:

void FrameEncoder::threadMain()
{
    THREAD_NAME("Frame", m_jpId);

    if (m_pool)	//若线程池不为空
    {
        m_pool->setCurrentThreadAffinity();	//设置当前线程

        /* the first FE on each NUMA node is responsible for allocating thread
         * local data for all worker threads in that pool. If WPP is disabled, then
         * each FE also needs a TLD instance */
        if (!m_jpId)
        {
            int numTLD = m_pool->m_numWorkers;
            if (!m_param->bEnableWavefront)
                numTLD += m_pool->m_numProviders;

            m_tld = new ThreadLocalData[numTLD];
            for (int i = 0; i < numTLD; i++)
            {
                m_tld[i].analysis.initSearch(*m_param, m_top->m_scalingList);
                m_tld[i].analysis.create(m_tld);
            }

            for (int i = 0; i < m_pool->m_numProviders; i++)
            {
                if (m_pool->m_jpTable[i]->m_isFrameEncoder) /* ugh; over-allocation and other issues here */
                {
                    FrameEncoder *peer = dynamic_cast<FrameEncoder*>(m_pool->m_jpTable[i]);
                    peer->m_tld = m_tld;
                }
            }
        }

        if (m_param->bEnableWavefront)
            m_localTldIdx = -1; // cause exception if used
        else
            m_localTldIdx = m_pool->m_numWorkers + m_jpId;
    }
    else	//若线程池为空,则WPP
    {
        m_tld = new ThreadLocalData;
        m_tld->analysis.initSearch(*m_param, m_top->m_scalingList);
        m_tld->analysis.create(NULL);
        m_localTldIdx = 0;
    }

    m_done.trigger();     /* signal that thread is initialized */ //线程已经触发
    m_enable.wait();      /* Encoder::encode() triggers this event */	//等待处理

    while (m_threadActive)
    {
        compressFrame();	//=====================调用compressFrame()
        m_done.trigger(); /* FrameEncoder::getEncodedPicture() blocks for this event */
        m_enable.wait();
    }
}


目录
相关文章
|
22天前
|
机器学习/深度学习 分布式计算 算法框架/工具
大模型的内部结构复杂,导致其决策过程难以解释,这对于某些应用场景来说是不可接受的。
【10月更文挑战第23天】随着人工智能技术的发展,越来越多的企业开始探索大模型的私有化部署。本文详细介绍了在企业内部实现大模型私有化部署的方法,包括硬件配置、数据隐私保护、模型可解释性提升以及模型更新和维护等方面的解决方案,帮助企业克服相关挑战,提高数据处理的安全性和效率。
31 4
|
数据库 芯片
如何使用GEOquery和limma完成芯片数据的差异表达分析
如何分析芯片数据 我最早接触的高通量数据就是RNA-seq,后来接触的也基本是高通量测序结果而不是芯片数据,因此我从来没有分析过一次芯片数据,而最近有一个学员在看生信技能树在腾讯课堂发布的课程GEO数据库表达芯片处理之R语言流程遇到了问题问我请教,为了解决这个问题,我花了一个晚上时间学习这方面的分析。
4283 0
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
大模型的特点、重要概念及工作方式详解
大模型是具有大量参数和复杂结构的深度学习模型,通过处理大量数据实现高效任务解决。其特点包括参数规模庞大、深层网络结构、预训练与微调、多任务学习和自适应能力。重要概念有注意力机制、Transformer架构、迁移学习和分布式训练。大模型的工作方式包括输入处理、特征提取、预测与损失计算、反向传播与优化,以及评估与微调。这些特性使其在自然语言处理、计算机视觉等领域取得显著进展。
|
4月前
|
机器学习/深度学习
现代深度学习框架问题之业务系统层面的广义控制流有哪些特性
现代深度学习框架问题之业务系统层面的广义控制流有哪些特性
|
4月前
|
数据库
业务系统架构实践问题之当一个模型既有独立性又有与其他模型的关联时,判断它是否为聚合根问题如何解决
业务系统架构实践问题之当一个模型既有独立性又有与其他模型的关联时,判断它是否为聚合根问题如何解决
|
存储 数据可视化 数据挖掘
知识点丨重测序数据进行kinship亲缘关系分析、构建IBS矩阵的方法与介绍
知识点丨重测序数据进行kinship亲缘关系分析、构建IBS矩阵的方法与介绍
知识点丨重测序数据进行kinship亲缘关系分析、构建IBS矩阵的方法与介绍
|
机器学习/深度学习 存储 程序员
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(一)
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(一)
188 1
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(一)
|
机器学习/深度学习 存储 算法
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(二)
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(二)
118 0
为什么LSTM看起来那么复杂,以及如何避免时序数据的处理差异和混乱(二)
|
计算机视觉
挣值分析 概念
挣值分析 概念
|
存储 自然语言处理
带你读《自然语言处理的认知方法》之一:延迟解释、浅层处理和构式:“尽可能解释”原则的基础
自然语言处理跨越了许多不同的学科,有时很难理解它们各自带来的贡献和挑战。本书探讨了自然语言处理与认知科学之间的关系,每章都由相关领域的专家撰写,内容涵盖语言理解、语言生成、词联想、词义消除歧义、词可预测性、文本生成和作者身份识别。本书适合对自然语言处理及其跨学科特性感兴趣的学生和研究人员。