如何通过SIMD优化4抽头Qpel参考读取函数,突破自动向量化瓶颈?
针对s_read_qpel_ref的SIMD优化方案
一、4抽头水平滤波的AVX2实现(核心瓶颈优化)
针对hor[v]的4抽头滤波,利用像素值≤10位的特性,用16位整数运算避免溢出,同时批量处理提升吞吐量:
- 预广播滤波系数到AVX2寄存器,一次处理8组像素(256位寄存器可容纳8个16位元素)
- 采用
_mm256_cvtepu8_epi16将8位像素扩展为16位,避免乘法溢出 - 用
_mm256_mullo_epi16完成批量乘,再通过_mm256_add_epi16累加结果,最后移位归一化(如果滤波需要固定除数)
// 假设hor[v]为4个int类型滤波系数,input为连续8位像素数组,output为输出数组 static inline __m256i qpel_4tap_filter(const uint8_t* input, const int* coeffs) { // 加载连续4组像素(对应4抽头的输入) __m256i p0 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i*)input)); __m256i p1 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i*)(input+1))); __m256i p2 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i*)(input+2))); __m256i p3 = _mm256_cvtepu8_epi16(_mm_loadu_si128((__m128i*)(input+3))); // 广播系数 __m256i c0 = _mm256_set1_epi16(coeffs[0]); __m256i c1 = _mm256_set1_epi16(coeffs[1]); __m256i c2 = _mm256_set1_epi16(coeffs[2]); __m256i c3 = _mm256_set1_epi16(coeffs[3]); // 乘加运算 __m256i sum = _mm256_add_epi16(_mm256_add_epi16(_mm256_mullo_epi16(p0, c0), _mm256_mullo_epi16(p1, c1)), _mm256_add_epi16(_mm256_mullo_epi16(p2, c2), _mm256_mullo_epi16(p3, c3))); // 假设滤波需要除以16,右移4位归一化 return _mm256_srai_epi16(sum, 4); }
二、修复int_buf跨步访问问题
跨步访问会导致SIMD加载效率骤降,解决方案是预转换为连续内存:
- 提前将
int_buf的跨步数据提取到连续临时数组,后续滤波直接访问连续内存 - 用AVX2指令批量加载跨步数据,减少循环开销
// 预转换int_buf为连续像素数组 void prepare_continuous_pixels(const uint8_t* int_buf, int stride, int v, int width, uint8_t* temp) { for (int x = 0; x < width; x += 8) { // 加载v行的x到x+7位置的跨步像素 __m256i row_data = _mm256_loadu_si256((__m256i*)&int_buf[v * stride + x]); _mm256_storeu_si256((__m256i*)&temp[x], row_data); } }
三、保留原有x循环的自动向量化
手写SIMD时不要完全替换x循环,仅封装滤波核心逻辑,让编译器优化边界处理等部分:
- 将滤波函数标记为
always_inline,在x循环中按8元素步长调用,剩余不足8的部分留给编译器自动向量化处理
void s_read_qpel_ref_optimized(...) { uint8_t temp_pixels[MAX_WIDTH]; prepare_continuous_pixels(int_buf, stride, v, width, temp_pixels); // 批量处理8元素块 for (int x = 0; x < width - 7; x += 8) { __m256i filtered = qpel_4tap_filter(&temp_pixels[x], hor[v]); // 将16位结果转回8位并存储 _mm256_storeu_si256((__m256i*)&pixel[x], _mm256_packus_epi16(filtered, filtered)); } // 剩余边界元素,编译器会自动向量化 for (int x = width - (width % 8); x < width; x++) { // 原有串行滤波逻辑或编译器自动生成的向量化代码 } }
四、16位乘法的额外优化点
- 由于像素值≤10位,4个乘积累加后最大值仅为
10*16*4=640,远小于16位整数上限,无需使用32位乘法,减少寄存器占用 - 归一化优先使用移位代替除法,比如除数为16时用
_mm256_srai_epi16,比除法指令快3-5倍
内容的提问来源于stack exchange,提问作者adi m
相关产品推荐
相关产品推荐

