【问题标题】:searching through a short sorted array of doubles搜索一个简短的排序数组
【发布时间】:2013-11-25 11:38:16
【问题描述】:

我正在尝试通过一个非常短的双精度排序数组来优化搜索,以定位给定value 所属的存储桶。假设数组的大小是 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]

因此检查 1 个存储桶需要 4 条指令(leavmovsdvucomisdjae)。假设value 是均匀分布的,平均而言,我必须检查每个value 约3.5 个桶。显然,这足以胜过前面列出的 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 做到这一点。而且我知道它可以 - 第一个示例中生成的 asm 是可以的。

除了使用内联 asm 之外,您有什么想法吗?甚至更好 - 您能建议更快地实现搜索吗?

【问题讨论】:

  • +1 但“我可以告诉 gcc 展开循环......在这种情况下,代码更慢”,对于 您的特定处理器乙>。我认为您的优化有点过于先进,风险是仅测试机器的最佳匹配架构(可能在其他方面表现不佳) .当然,如果您没有针对 CPU 固定且不会随时间变化的嵌入式系统进行优化。
  • @Adriano 我不准确 - 具有一般边界的循环的展开代码比为具有恒定边界的循环生成的代码慢。我不希望这非常依赖于平台 - 只是少了 2 条指令。还是我错过了什么?
  • 这里我只是猜测,因为我没有看到 gcc 生成了什么,但我会检查缓存大小(一级缓存)、分支预测或管道内容
  • @angainor “现在,在一般情况下,数组当然可能大于 8 个元素”,正如您所提到的,它是一个排序数组,O(log(n)) 搜索可能(稍微)提高性能。
  • @starrify 谢谢,当然我已经为大型阵列做到了。我有一个八进制树结构——这就是为什么我需要在一个包含 8 个元素的数组中进行搜索。另外,我需要在树叶中进行一般的简短搜索。

标签: c gcc assembly compiler-optimization micro-optimization


【解决方案1】:

不是一个真正的答案,但在 cmets 中没有空间。

我针对一个简单的 C 实现测试了 AVX 函数,得到了完全不同的结果。 我在 Windows 7 x64 而不是 Linux 上进行了测试,但生成的代码非常相似。 测试是如何进行的: 1) 我禁用了 CPU 的 SpeedStep。 2) 在 main() 中,我将进程优先级和线程优先级提高到最大(实时)。 3)我对测试函数运行了 10M 次调用来加热 CPU - 激活 turbo。 4) 我调用了 Sleep(0) 来避免上下文切换 5) 我调用了 __rdtscp 开始测量 6)在一个循环中,我调用了 AVX 查找索引函数或简单的 C 版本 - 就像你做的那样。另一个实现被注释掉并且没有被使用。循环大小为 10M 调用。 7) 我再次调用 __rdtscp 以完成基准测试。 8)我打印了刻度/迭代。获取呼叫的平均滴答计数

注意:我将两个“查找索引”函数都声明为内联函数,并在反汇编中确认它们已内联。 AVX 函数和您描述的 C 函数不相同,C 函数返回从零开始的索引,而 AVX 函数返回从 1 开始的索引。 在我的系统上,AVX 函数每次迭代需要 1.1 个周期,而 C 函数每次迭代需要 4.4 个周期。

我不能强制 MSVC 编译器使用超过 ymm 的寄存器:(

使用的数组:

double A[8] = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8 };

结果(平均滴答数/迭代): 值 = 0.3(索引 = 2):AVX:1.1 | C: 4.4 值 = 0.5(索引 = 3):AVX:1.1 | C: 11.1 值 = 0.9(索引 = 7):AVX:1.1 | C: 18.1

如果 AVX 函数被更正为返回 pos-1,那么它会慢 50%。 您可以看到 AVX 函数在恒定时间内工作,而琐碎的 C 循环函数的性能取决于您要查找的索引。

使用 clock() 计时并运行 100M 会产生类似的结果,AVX 在第一次测试时几乎快 4 倍。 另请注意,运行较长时间的测试会显示不同的结果,但每次 AVX 都具有相似的优势。

【讨论】:

  • 感谢您的努力!所以为静态 C 函数生成的 asm 代码就像我发布的那样。您是否对随机生成的数据进行了测试?此外,AVX 代码似乎不可能只需要 1 个周期。您可以查看时间测量值而不是 rdtscp 吗?我将尝试使用您提到的步骤运行测试。我之前在不同的上下文中对代码进行了计时 - 在树遍历实现中。结果可能会有所不同,尽管我不确定为什么会这样。一件事 - 为什么pos-1 会大幅降低性能??
  • 我使用了相同的数据(数组),只要对数组进行排序,指令的行为都是一样的。 CPU 对计算进行流水线处理,并拥有多种资源来进行 SSE/AVX 计算,因此平均约 1 条指令是非常可行的。分支预测命中率应为 99.999999%,因为循环简单且长。解码也很快,因为它一次又一次地使用相同的 6-8 条指令。在任何情况下,多次运行的结果都非常一致。在 C 函数中,分支预测不太好,因为循环很短。我可以尝试用墙上时间来测量。
  • 我已经尝试过,在单个静态 array 的情况下,代码被进一步(不切实际地)优化以将 array 元素的负载移到循环之外。这有点太多了;)在这种情况下,测试 10e7 SORTED 输入数组values,AVX 位置每个值需要 4.2 个周期,展开的“循环”每个值需要 5.2 个周期。 AVX 有点快(!)。 1 个时钟周期是不现实的(至少在我的机器上),因为在读取 values 时会超出内存带宽。除非您不阅读这些内容,否则在整个循环期间所有内容都在寄存器中...
  • 这里的内存带宽为零,所有内容都很快在 L1 缓存中结束 - 代码和数据。至于代码,因为它太小了,它的 uOP 被缓存在处理器 DSB 单元中......总而言之,这种优化在你的整个应用程序中可能并不重要
  • value 存储在一个共有 10e7 个值的数组中。当然,我对在一组静态 bin 中重复搜索相同的值不感兴趣。在实际代码中,搜索的值和 bin 都会发生变化。但是感谢您的努力,我会做更多的工作,看看是否可以做任何事情..
【解决方案2】:

您可以尝试整数比较。双重比较等效于对相同位进行 int64_t 比较,但 NaN 除外。它可以转得更快。 CPU 具有比 SIMD 更多的整数执行单元。只需发送 double* 并在函数参数中接收 int64_t*。

【讨论】:

    猜你喜欢
    • 2020-08-13
    • 1970-01-01
    • 1970-01-01
    • 2021-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多