源地址:http://falloutmx.blog.163.com/blog/static/3923602020101024914425/
Intel的MMX技术是对Intel体系结构(IA)指令集的扩展。该技术使用了单指令多数据技术(SIMD)技术,以并行方式处理多个数据元素。MMX指令集增加了57条新的操作码和一个新的64位四字数据类型,增加了八个新的64位MMX寄存器,每个寄存器可按名称MM0-MM7直接访问。这意味着我们可以在一个寄存器里最多存8个数据(最小单位字节)。
SSE系列是MMX的超集,直到SSE2才跟MMX有本质的区别,添加了对64位双精度浮点数的支持,以及对整型数据的支持,也就是说这个指令集中所有的MMX指令都是多余的了,同时也避免了占用浮点数寄存器。这个指令集还增加了对CPU的缓存的控制指令。新的SSE寄存器命名为xmm0~xmm7,每个128位。
对H264的优化主要用到了MMX和SSE2。举个例子,在H264解码端的IDCT和QUANT里,要操作的数是一个4X4的矩阵,很适合拿来优化。 IDCT计算公式:
![MMX和SSE的运用 - 菜包 - falloutmx的博客](http://img.ph.126.net/brL28in-pxvk7dkWacb-mA==/3321686200163274956.png)
其中,“Ei”已在前面的反量化中完成,其结果为 Wr,所以这里只进行以下运算:
![MMX和SSE的运用 - 菜包 - falloutmx的博客](http://img161.ph.126.net/E2coa3FD2ElKVGoX4S6cyA==/2154690946721602952.jpg)
memcpy(&m7,(&img->cof[i0][j0][0][0]),sizeof(short)*16); //如果不能采用数组形式的话,输入输出将十分的麻烦。只能通过移位指令,浪费大量的时间。
_mm_prefetch(&m7,_MM_HINT_NTA); // _mm_prefetch将数据预存到cache里,一次存一个cache line的数据,_MM_HINT_NTA表示存到离CPU最近的cache里(既L1 cache)。在操作开始前执行_mm_prefetch可以避免由于读未命中产生的大量延时。
_asm{
movq mm0,[m7]// movq尽可能的在一起, 前提是在一起的mov不要使用一样的mmx寄存器
movq mm1,[m7+8] //2.连续的读指令可以配对,当指令可以配对的时候,一个时钟周期可以同时处理这两条指令(U管道与V管道)。最好避免AGI延迟
movq mm2,[m7+16]
movq mm3,[m7+24] //通过MOVQ指令一次处理四个向量元素,并通过PADDW指令一次完成四个加法。将使速度提高四倍
paddw mm2,mm0 //m6[0]=(m5[0]+m5[2]);每个指令前都有个“P”开头。这个“P”的意思就是——Packet,就是说每个寄存器里的数据都是个数据包,而不是一个数据。指令最后的w表示按数据包里每个数据的大小是“word”, addw既paddw mm0,mm0
psubw mm0,mm2 m6[1]=(m5[0]-m5[2]);
movq mm4,mm1 psraw mm4, 1 (m5[1]>>1) //基本的优化常识,乘2和除2操作用移位代替。如果有必要使用常数,那么使用立即数通常比把常数读取到寄存器更有效。但是如果同一个立即数被多次引用,应把常数先取到一个寄存器,这种方法更快
psubw mm4,mm3 // m6[2]=(m5[1]>>1)-m5[3];
psraw mm3, 1 // (m5[3]>>1) //由于只有一个mmx移位寄存器, 移位分组指令是不能配对的
paddw mm1,mm3 // m6[3]=m5[1]+(m5[3]>>1);
paddw mm4,mm0 // img->m7[0][j]=m6[0]+m6[3];
paddw mm0,mm0
psubw mm0,mm4 // img->m7[3][j]=m6[0]-m6[3];
paddw mm1,mm2 // img->m7[1][j]=m6[1]+m6[2];
paddw mm2,mm2
psubw mm2,mm1 // img->m7[2][j]=m6[1]-m6[2];
//矩阵倒换,拆包和打包指令可以用于矩阵行列倒换
m5[j]=img->m7[i][j];
![MMX和SSE的运用 - 菜包 - falloutmx的博客](http://img617.ph.126.net/ZCrFOse4TBLdXHG4wEVcJA==/1903615267495796746.jpg)
movq mm5,mm0
movq mm3,mm1
PUNPCKlwd mm1,mm4
PUNPCKhwd mm3,mm4
PUNPCKlwd mm0,mm2
PUNPCKhwd mm5,mm2
movq mm6,mm1
movq mm7,mm3
PUNPCKldq mm1,mm0
PUNPCKhdq mm6,mm0
PUNPCKldq mm3,mm5
PUNPCKhdq mm7,mm5
//第二次一维变换 ,跟第一次完全一样
movq mm4,mm6
paddw mm3,mm1 // m6[0]=(m5[0]+m5[2]);
paddw mm1,mm1
psubw mm1,mm3 // m6[1]=(m5[0]-m5[2]);
psraw mm4,1
psubw mm4,mm7 // m6[2]=(m5[1]>>1)-m5[3];
psraw mm7,1
paddw mm6,mm7 //m6[3]=m5[1]+(m5[3]>>1);
paddw mm4,mm1 // img->m7[0][j]=m6[0]+m6[3];
paddw mm1,mm1
psubw mm1,mm4 // img->m7[3][j]=m6[0]-m6[3];
paddw mm6,mm3 // img->m7[1][j]=m6[1]+m6[2];
paddw mm3,mm3
psubw mm3,mm6 // img->m7[2][j]=m6[1]-m6[2];
movq [m7+24],mm3 //在优化的时候,数据对齐是很关键的。比如说在这个地方,如果内存8位对齐的话,是一个64位写,否则2个32位写。
movq [m7+16],mm1
movq [m7+8],mm4
movq [m7],mm6
EMMS // 由于MMX用的是浮点寄存器,使用EMMS指令清MMX寄存器并置浮点标志字的值为空(即为全1)。该指令应在代码段结束时插入,以避免执行浮点代码时产生浮点栈的溢出错误。这条指令将耗费20-50个时钟周期。
}
像素恢复操作:
img->m7[i][j] =max(0,min(img->max_imgpel_value,( img->m7[i][j] +((long)img->mpr[i+ioff][j+joff] <<6)+32)>>6));
img->m7[i][j1]=max(0,min(img->max_imgpel_value,( img->m7[i][j1]+((long)img->mpr[i+ioff][j1+joff]<<6)+32)>>6));
MMX里没有max和min相关的指令操作,因此用SSE2进行优化。Intel提供了intrinsics库让我们写SSE代码更方便快速,但是这样会比汇编代码要慢,因为它不在乎寄存器的分配和重新使用。
举个例子:
temp_m70_sse=_mm_slli_epi16(temp_m70_sse,6);
_asm{
movdqa xmm0,xmmword ptr [temp_m70_sse]
psllw xmm0,6
movdqa xmmword ptr [temp_m70_sse],xmm0
}
temp_m70_sse=_mm_add_epi16(temp_m70_sse,temp_32_sse);
_asm{
movdqa xmm0,xmmword ptr [temp_m70_sse]
movdqa xmm1,xmmword ptr [temp_32_sse]
paddw xmm0,xmm1
movdqa xmmword ptr [temp_m70_sse],xmm0
}
可以看到,运用这些指令的时候,每条指令都被单独的处理,没有考虑前后相关性,导致数据多次进行存取,降低了速度,有可能比原来的C语言更慢。 剩下没什么好说的,优化后的代码如下:
temp_32_sse=_mm_set1_epi16(32);
m70_sse=_mm_set_epi16(i16a[13],i16a[9],i16a[5],i16a[1],i16a[12],i16a[8],i16a[4],i16a[0]); //由于要处理的数据在内存上不是连续的,因此把这些数据读到xmm寄存器里会耗费大量时间,编译后看反汇编的的代码就知道了。 m71_sse=_mm_set_epi16(i16a[15],i16a[11],i16a[7],i16a[3],i16a[14],i16a[10],i16a[6],i16a[2]); temp_255_sse=_mm_set1_epi16(img->max_imgpel_value); temp_zero_sse=_mm_set1_epi16(0); temp_m70_sse=_mm_set_epi16(img->mpr[i1_ioff][3+joff],img->mpr[i1_ioff][2+joff],img->mpr[i1_ioff][1+joff],img->mpr[i1_ioff][joff], img->mpr[i0_ioff][3+joff],img->mpr[i0_ioff][2+joff],img->mpr[i0_ioff][1+joff],img->mpr[i0_ioff][joff]);
temp_m71_sse=_mm_set_epi16(img->mpr[i3_ioff][3+joff],img->mpr[i3_ioff][2+joff],img->mpr[i3_ioff][1+joff],img->mpr[i3_ioff][joff], img->mpr[i2_ioff][3+joff],img->mpr[i2_ioff][2+joff],img->mpr[i2_ioff][1+joff],img->mpr[i2_ioff][joff]);
_asm{
movdqa xmm0,xmmword ptr [temp_m70_sse]
movdqa xmm1,xmmword ptr [temp_32_sse]
movdqa xmm2,xmmword ptr [temp_m71_sse]
psllw xmm0,6
movdqa xmmword ptr [temp_m70_sse],xmm0
paddw xmm0,xmm1
movdqa xmmword ptr [temp_m70_sse],xmm0
psllw xmm2,6
paddw xmm2,xmm1
movdqa xmm1,xmmword ptr [m70_sse]
movdqa xmm3,xmmword ptr [m71_sse]
paddw xmm0,xmm1
paddw xmm2,xmm3
psraw xmm0,6
psraw xmm2,6
movdqa xmmword ptr [temp_m70_sse],xmm0
movdqa xmmword ptr [temp_m71_sse],xmm2
movdqa xmm1,xmmword ptr [temp_255_sse]
movdqa xmm3,xmmword ptr [temp_zero_sse]
pminsw xmm0,xmm1
pmaxsw xmm0,xmm3
pminsw xmm2,xmm1
pmaxsw xmm2,xmm3
packuswb xmm0,xmm2
movdqa xmmword ptr [temp_m70_sse],xmm0 //SSE不需要EMMS操作
}
_mm_storeu_si128(img_m7,temp_m70_sse);//sse使用专用的结构_m128i,因此最后需要存到我们自己的数组里