我正在尝试通过一组非常短的已排序的双精度数组来优化搜索,以定位给定的value
所属的 bucket。假设数组大小为8个双精度数,我已经想出了以下AVX指令序列:
_data = _mm256_load_pd(array);
temp = _mm256_movemask_pd(_mm256_cmp_pd(_data, _value, _CMP_LT_OQ));
pos = _mm_popcnt_u32(temp);
_data = _mm256_load_pd(array+4);
temp = _mm256_movemask_pd(_mm256_cmp_pd(_data, _value, _CMP_LT_OQ));
pos += _mm_popcnt_u32(temp);
出乎意料(我没有指令延迟规格在我的脑海中..),gcc为以下C循环生成了更快的代码:
for(i=0; i<7; ++i) if(array[i+1]>=value) break;
这个循环编译后变成了非常高效的代码:
lea ecx, [rax+1]
vmovsd xmm1, QWORD PTR [rdx+rcx*8]
vucomisd xmm1, xmm0
jae .L7
lea ecx, [rax+2]
vmovsd xmm1, QWORD PTR [rdx+rcx*8]
vucomisd xmm1, xmm0
jae .L8
[... repeat for all elements of array]
所以,每检查一个存储桶需要4个指令(
lea
, vmovsd
, vucomisd
, jae
)。假设value
均匀分布,平均而言,我需要检查约3.5个存储桶才能找到相应的value
。显然,这足以超越前面列出的AVX代码。现在,在一般情况下,数组可能大于8个元素。如果我像这样编写C循环:
for(i=0; u<n-1; i++) if(array[i+1]>=value) break;
我会尽力为您翻译以下内容:
我获得了循环体的以下指令序列:
.L76:
mov eax, edx
.L67:
cmp eax, esi
jae .L77
lea edx, [rax+1]
mov ecx, edx
vmovsd xmm1, QWORD PTR [rdi+rcx*8]
vucomisd xmm1, xmm0
jb .L76
我可以让gcc展开循环,但每个元素的指令数量比具有常量边界的循环更大,代码会变慢。此外,我不明白在vmovsd中使用额外的rcx寄存器进行寻址的原因。
我可以手动修改循环的汇编代码,使其类似于第一个示例,这样做确实可以更快。
.L76:
cmp edx, esi # eax -> edx
jae .L77
lea edx, [rdx+1] # rax -> rdx
vmovsd xmm1, QWORD PTR [rdi+rdx*8]
vucomisd xmm1, xmm0
jb .L76
但是我似乎无法让gcc实现它。我知道它可以 - 第一个示例中生成的汇编代码是正确的。
您有没有其他想法可以不使用内联汇编来实现它?或者更好的是 - 您能否建议一种更快的搜索实现方式?