借助多线程能力和英特尔(r) 高性能多媒体函数库(Intel(r) IPP)加速视频编码例程

26 views
Skip to first unread message

杨斌

unread,
Aug 31, 2007, 11:19:18 PM8/31/07
to earl...@googlegroups.com
作者:Kiefer Kuah

视频编码器中常用的"绝对差值求和"例程已进行了优化,这是通过将例程分解为多个线程并使用英特尔® 高性能多媒体函数库(英特尔® IPP)来实现的。对于单个四核处理器,多线程实现的性能加速达 3.84 倍。英特尔® IPP 函数又使增益提高了 1.45 倍,从而四核总体性能加速达 5.55 倍。这种方法不需要重新编写大量的代码,可快速实现。
简介
在过去的一年中,越来越多的台式机和笔记本电脑开始采用双核处理器,这应该是软件开发商开发多线程应用的极具说服力的原因。向更高级别并行计算发展的趋势势不可挡。英特尔® 酷睿™2 双核处理器是一款双核处理器。该处理器的四核版(即英特尔® 酷睿™2 四核处理器)已于最近推出。为充分利用多核的优势,软件应用需要进行多线程处理。在任一给定时间,单线程的应用只能在单核上执行,而使其他内核处于空闲状态。而多线程应用中,工作负载被划分为相等的块,平均分配给各个可用内核。要获得多线程的最佳性能,确保分配给各个内核的工作负载完全平衡是至关重要。本文中,我们以一个视频编码应用为示例,该应用中将例程多线程化获得的加速比几乎与核的数量成线性比例。对于单个四核处理器,多线程实现的加速比为 3.84 倍。

视频编码中计算密集型运算之一是运动估计(motion estimation)。此运算占用编码器中大约 40% 或更多的计算周期。搜索运动向量就是将某一帧中的某一像素块与参考帧中的多个像素块进行比较,从而找出在参考帧中与之最相似的像素块。工作量将随着数据集的大小(此例中是视频帧的大小)而增加。

图 1 中显示了执行此运动向量搜索运算的部分代码。在双嵌套循环中将调用 blockMatch 函数。此示例中,调用的次数为 frameHeight/blockHeight* frameWidth/blockWidth,其中 frameHeight 和 frameWidth 是以像素数表示的帧高度和帧宽度,blockHeight 和 blockWidth 是以像素数表示的块高度和块宽度。搜索范围通常限制在参考帧内的某一区域,这样可以减少此函数被调用的次数。此函数通过指针被传递到参考帧以及当前帧中的当前块,并计算参考帧中与当前块最匹配的块。结果将存储在传递到此函数的"matchBlock[i][j]"自变量中。

图 1:在嵌套循环中调用 blockMatch 例程。
	for (int i=0; i<frameHeight/blockHeight; i++)
	{
		for (int j=0; j<frameWidth/blockWidth; j++)
		{
			blockMatch(refFrame, stepBytesRF,
curFrame+stepBytesCF*i+j*blockWidth, stepBytesCF, matchBlock[i][j]);
		}
	}

此代码即通过 OpenMP* 进行了多线程化。虽然也可以使用 Windows* 线程化 API,但在此例中使用 OpenMP 更为简单。只需在循环前添加 OpenMP 的编译指示指令即可实现(图 2)。此编译指示指令将指示编译器对循环进行线程化处理。要使用的线程的数量是在运行时根据系统中可用的内核数量动态确定的。也可以在编程时确定应用中要使用的线程数量,但是只有在有充足的理由时,才这样做。英特尔® 编译器 9 和 Microsoft Visual Studio 2005* 均支持 OpenMP。

图 2:使用 OpenMP 进行多线程化处理。
#pragma omp parallel for
	for (int i=0; i<frameHeight/blockHeight; i++)
	{
		for (int j=0; j<frameWidth/blockWidth; j++)
		{
			blockMatch(refFrame, stepBytesRF,
curFrame+stepBytesCF*i+j*blockWidth, stepBytesCF, matchBlockOptimized[i][j]);
		}
	}

接下来,我们使用 IPP 5.1 中已优化的函数替换了 C++ 代码(图 3)。该 IPP 函数计算参考块和当前块之间的"绝对差值求和"。IPP 中提供了 7 个"绝对差值求和"函数,这些函数适用于不同的块大小。

图 3:调用 IPP 函数 ippiSAD4x4_8u32s。
	for (int j=0; j<frameWidth-blockWidth; j++)
	{
		temSum = 0;
		pCur = curBlock;
		pRef = refFrame+i*stepBytesRF+j;
		ippiSAD4x4_8u32s(pCur, stepBytesCB, pRef,
stepBytesRF, &temSum, IPPVC_MC_APX_FF);
		if (temSum < lowSum)
		{
			lowSum = temSum;
			matchBlock[0] = j;
			matchBlock[1] = i;
		}
	}

结果
分别在英特尔® 酷睿™2 双核处理器和英特尔® 酷睿™2 四核处理器上,对运动向量搜索例程进行测试。该线程例程获得的加速比几乎与内核数量成线性比例。它在双核处理器上产生的加速比为 1.89 倍;在四核处理器上产生的加速比为 3.84 倍(表 1)。通过使用优化的 IPP 函数,可获得更大的增益。IPP 函数本身产生的加速比介于 1.39 倍 和 1.45倍之间。与多线程化结合使用,双核处理器即可实现 2.67 倍的加速比,四核处理器可实现加 5.55 倍的加速比。

IPP 函数中包含 SIMD 指令。在初始化期间,IPP 库检测处理器的特性并确定支持哪些单指令多数据流指令集扩展(SSE),从而调度该处理器支持的最优化版本的函数。使用 IPP 就无需自己编写 SIMD 优化的函数。
 
表 1:以毫秒计的消耗时间。加速比是以 C++ 函数消耗时间为基准进行计算的。在这些测试中,帧大小和块大小设置为下列值:frameWidth = 128,frameHeight = 128,blockWidth = 4,blockHeight = 4。
  2.67 GHz 英特尔 酷睿™2 双核 2.67 GHz 英特尔 酷睿™2 四核
函数 时间(毫秒) 加速比 时间(毫秒) 加速比
C++ 355.82 1.00 倍 356.08 1.00 倍
C++ 线程化 188.12 1.89 倍 92.68 3.84 倍
IPP 优化 256.21 1.39 倍 245.1 1.45 倍
IPP 优化
线程化
133.21 2.67 倍 64.13 5.55 倍

在应用中并行代码与串行代码的比决定了多线程实现的加速比大小。假设 p 是应用中可以并行化的代码的比率,n 是内核数量,则根据阿姆达尔定律,可能的加速比为

        加速比      =      1 / (1-p+p/n)

随着并行化程度的提高(即随着 n 的增加),p/n 项将越来越小。随着 n 相比 p 越来越大,p/n 趋近于 0,则等式右端便剩下 1/(1-p)。我们知道 (1-p) 是串行代码的比率。换句话说,在提高应用并行部分的并行化程度时,限制加速比的因素是串行代码。

我们可以估测优化的运动估计中增益对整体性能的影响程度。如果运动向量搜索约占 40% 编码工作,并且使用 IPP 及 4 个线程以 5.55 倍加速,则整个编码器的理论加速比为 1.49 倍。
结论
在应用开发过程中,用于提升性能的多线程化处理通常只是后期考虑。随着双核处理器和四核处理器的越来越普及,需要有针对性地进行多线程化处理提高关键代码的性能。您可以不再依赖于处理器频率的增加来提高应用的性能或功能组合。在多核处理器中运行时,经过适当多线程化处理的代码可以获得显著的加速比。在该运动估计示例中获得的加速比接近理论极限。该示例只是编码器中的几个组件之一。还可以检查编码器中的其他组件,以发现进行分解和线程化处理的可能性。
Reply all
Reply to author
Forward
0 new messages