最近在面试,被大佬问到为什么x264能比JM快这么多,确实以前没有仔细研究过,记录一下学习代码的过程,如果有错误希望大家能帮我指出来。现有能搜到的许多经验贴代码版本与现在的版本不太一致,本文基于x264官方给出的最新版本的代码。
下面从cache友好的方面来分析x264中的x264_scan8变量
首先简述一下什么是cache友好
程序的性能就是指执行程序所用的时间,显然程序的性能与程序执行时访问指令和数据所用的时间有很大关系,而指令和数据的访问时间与相应的 Cache 命中率、命中时间和和缺失损失有关。对于给定的计算机系统而言,命中时间和缺失损失是确定的。因此,指令和数据的访存时间主要由 Cache 命中率决定,而 Cache 的命中率则主要由程序的空间局部性和时间局部性决定。
也就是说当我们访问一个内存地址单元时会将同一块的的数据同时从内存读入到Cache,这样如果我们继续访问附近的数据,那它就已经位于Cache中,访问速度就会很快。
接下来就是看看x264如何通过x264_scan8实现cache友好
scan8是和cache配合使用的,cache可以看成一个表格的话。scan8[]则存储的是宏块信息在cache中的索引值。
话不多说 先贴代码,看看scan8是啥
static const uint8_t x264_scan8[16*3 + 3] =
{ // 亮度值
4+ 1*8, 5+ 1*8, 4+ 2*8, 5+ 2*8,
6+ 1*8, 7+ 1*8, 6+ 2*8, 7+ 2*8,
4+ 3*8, 5+ 3*8, 4+ 4*8, 5+ 4*8,
6+ 3*8, 7+ 3*8, 6+ 4*8, 7+ 4*8,
// Cb值
4+ 6*8, 5+ 6*8, 4+ 7*8, 5+ 7*8,
6+ 6*8, 7+ 6*8, 6+ 7*8, 7+ 7*8,
4+ 8*8, 5+ 8*8, 4+ 9*8, 5+ 9*8,
6+ 8*8, 7+ 8*8, 6+ 9*8, 7+ 9*8,
// Cr值
4+11*8, 5+11*8, 4+12*8, 5+12*8,
6+11*8, 7+11*8, 6+12*8, 7+12*8,
4+13*8, 5+13*8, 4+14*8, 5+14*8,
6+13*8, 7+13*8, 6+14*8, 7+14*8,
// DY/DU/DV are for luma/chroma DC.
0+ 0*8, 0+ 5*8, 0+10*8
};
scan8中按照顺序分别存储了Y,U,V信息在cache中的索引值。但是具体的存储还是在相应的cache中。cache中首先存储Y,然后存储U和V。cache中的存储方式如下所示。其中数字代表了scan8[]中元素的索引值(从0到50),这里和之前看到的别人的贴子不太一样,新的版本支持了更多的输入格式,不仅是420
* +---+---+---+---+---+---+---+---+---+
* | | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
* +---+---+---+---+---+---+---+---+---+
* | 0 | 48| | | y| y| y| y| y|
* | 1 | | | | y| 0| 1| 4| 5|
* | 2 | | | | y| 2| 3| 6| 7|
* | 3 | | | | y| 8| 9| 12| 13|
* | 4 | | | | y| 10| 11| 14| 15|
* | 5 | 49| | | u| u| u| u| u|
* | 6 | | | | u| 16| 17| 20| 21|
* | 7 | | | | u| 18| 19| 22| 23|
* | 8 | | | | u| 24| 25| 28| 29|
* | 9 | | | | u| 26| 27| 30| 31|
* |10 | 50| | | v| v| v| v| v|
* |11 | | | | v| 32| 33| 36| 37|
* |12 | | | | v| 34| 35| 38| 39|
* |13 | | | | v| 40| 41| 44| 45|
* |14 | | | | v| 42| 43| 46| 47|
* |---+---+---+---+---+---+---+---+---+
为什么这样做?
首先假设如果不这样做,那么按照一般的做法,应该是直接读取fenc平面中的当前宏块的图像像素值并且把它们放在cache里面待后面的帧内帧间预测时使用。但其实在后面帧内以及帧间预测的时候,不仅仅需要像素值,还需要top以及left块的像素信息以及相关的参数。因为没有做特殊处理,cache当中是没有这些信息的,当然更谈不上命中,基本每次都需要从存储空间中读取,显然这不是高效的做法。
相比较而言,x264中使用的是大于原块尺寸的空间来存储每个4x4的块的亮度以及色度信息,多余的空白空间(见上面)可以把相邻块的重建像素值也保存到cache内(y,u,v字母代表的位置)。除此以外还有每个分量DC值,CAVLC编码过程中需要的当前块的left块和top块的non_zero_count 以及相关的其他的一些参数,这样就可以保证在进行帧内帧间预测时候,绝大部分数据都在cache当中,可以非常高效的进行计算。
还是举个小栗子
void x264_mb_predict_mv_16x16( x264_t *h, int i_list, int i_ref, int16_t mvp[2] )
{
int i_refa = h->mb.cache.ref[i_list][X264_SCAN8_0 - 1];
int16_t *mv_a = h->mb.cache.mv[i_list][X264_SCAN8_0 - 1];
int i_refb = h->mb.cache.ref[i_list][X264_SCAN8_0 - 8];
int16_t *mv_b = h->mb.cache.mv[i_list][X264_SCAN8_0 - 8];
int i_refc = h->mb.cache.ref[i_list][X264_SCAN8_0 - 8 + 4];
int16_t *mv_c = h->mb.cache.mv[i_list][X264_SCAN8_0 - 8 + 4];
if( i_refc == -2 )
{
i_refc = h->mb.cache.ref[i_list][X264_SCAN8_0 - 8 - 1];
mv_c = h->mb.cache.mv[i_list][X264_SCAN8_0 - 8 - 1];
}
int i_count = (i_refa == i_ref) + (i_refb == i_ref) + (i_refc == i_ref);
if( i_count > 1 )
{
median:
x264_median_mv( mvp, mv_a, mv_b, mv_c );
}
else if( i_count == 1 )
{
if( i_refa == i_ref )
CP32( mvp, mv_a );
else if( i_refb == i_ref )
CP32( mvp, mv_b );
else
CP32( mvp, mv_c );
}
else if( i_refb == -2 && i_refc == -2 && i_refa != -2 )
CP32( mvp, mv_a );
else
goto median;
}
可以结合上面代码看出,在预测运动向量的时候,参考的是预先存在cache中的相邻块的信息
(转载请注明出处。)
博客详细分析了x264编码器为何比JM快,主要从cache友好的角度出发,探讨x264中x264_scan8变量的作用。x264通过优化存储方式,提高Cache命中率,从而提升性能。在处理宏块信息时,x264不仅存储当前块,还包括相邻块的像素值和相关参数,确保帧内帧间预测时大部分数据已在Cache中,实现高效计算。举例说明了在预测运动向量时如何利用预先存在的Cache信息。
2355

被折叠的 条评论
为什么被折叠?



