【发布时间】:2017-12-16 12:15:53
【问题描述】:
我正在开发一个应用程序,高度优化的线性搜索将对整体性能产生重大影响,我的任务是尽可能提高性能。
我在一个由 10,000 个元素组成的向量上运行我的搜索,该向量最后以标记值为界,我在距目标元素一定距离处运行线性搜索并测量找到该元素所花费的时间。我从一组元素中随机选择目标元素,这些元素位于距数组开头的恒定距离之后,以允许开始搜索。我正在使用Google's benchmark framework 衡量性能。
我收集的结果让我感到惊讶。我预计在某些时候 SIMD 会在性能方面击败展开的循环,但是随着移动阵列所需距离的增加,两者之间的差距似乎也在扩大。此外,我不确定为什么展开 8 次的循环在较短的距离上比展开 32 次的循环运行得更快。
Benchmark Time CPU Iterations
---------------------------------------------------------------------
BM_Search<linUnroll<8>>/2 86 ns 86 ns 7699241
BM_Search<linUnroll<8>>/4 103 ns 103 ns 6797378
BM_Search<linUnroll<8>>/16 650 ns 650 ns 1079095
BM_Search<linUnroll<8>>/64 1365 ns 1365 ns 514196
BM_Search<linUnroll<8>>/256 3558 ns 3558 ns 196519
BM_Search<linUnroll<8>>/1024 12358 ns 12358 ns 56635
BM_Search<linUnroll<8>>/4096 47341 ns 47341 ns 14780
BM_Search<linUnroll<8>>/8192 95029 ns 95030 ns 7367
BM_Search<linUnroll<32>>/2 131 ns 131 ns 5337221
BM_Search<linUnroll<32>>/4 131 ns 131 ns 5329296
BM_Search<linUnroll<32>>/16 291 ns 291 ns 2404646
BM_Search<linUnroll<32>>/64 836 ns 836 ns 831093
BM_Search<linUnroll<32>>/256 2776 ns 2776 ns 252901
BM_Search<linUnroll<32>>/1024 10962 ns 10962 ns 63828
BM_Search<linUnroll<32>>/4096 41312 ns 41312 ns 16941
BM_Search<linUnroll<32>>/8192 83303 ns 83304 ns 8401
BM_Search<linSIMD>/2 163 ns 163 ns 4304086
BM_Search<linSIMD>/4 208 ns 208 ns 3354716
BM_Search<linSIMD>/16 366 ns 366 ns 1912122
BM_Search<linSIMD>/64 871 ns 871 ns 803854
BM_Search<linSIMD>/256 3333 ns 3334 ns 210159
BM_Search<linSIMD>/1024 11262 ns 11262 ns 62157
BM_Search<linSIMD>/4096 42656 ns 42656 ns 16413
BM_Search<linSIMD>/8192 87824 ns 87824 ns 7970
我在 i5-4570 上运行,并且遵守了 clang 5.0.0。 quick-bench 没有 AVX,并且 clang 在 3.8 版本中没有完全展开,但它应该是可运行的。我也尝试展开 SIMD,以及转到 AVX256 指令,但都使性能变差。为什么展开的循环要快得多?为什么展开次数多的循环比展开次数少的循环执行得差这么多?
SIMD 的经典诊断是您在 SIMD 中没有做足够的工作,但我认为我在这里做的工作已经足够了。
#include <vector>
#include <cinttypes>
#include <immintrin.h>
typedef int V;
typedef std::vector<V> vi;
long linSIMD(const vi& arr, const long guessIx, const V x) {
using v4 = V __attribute__ ((vector_size (4*4)));
using dv2 = int64_t __attribute__ ((vector_size (4*4)));
constexpr int roll = 4;
constexpr union {
int32_t i32[2];
int64_t i64;
} skip = {-2,-2};
v4 xVec = {x,x,x,x};
for (int i = guessIx;; i += roll) {
v4 arrVec;
for (long j = 0; j < 4; j++) arrVec[j] = arr[i+j];
union {
v4 i32;
dv2 i64;
} cmpVec = {arrVec < xVec};
v4 cmpVec2 = {cmpVec.i32[3], cmpVec.i32[2], cmpVec.i32[1],cmpVec.i32[0]};
cmpVec.i32 += cmpVec2;
if (cmpVec.i64[0] == skip.i64) continue;
return i - cmpVec.i32[0] - cmpVec.i32[1];
}
}
long linUnroll32(const vi& arr, const long guessIx, const V x) {
constexpr int roll = 32;
for (long i = guessIx;; i += roll)
for (long j = 0; j < roll; j++)
if (arr[i+j] >= x) return i+j;
}
http://quick-bench.com/_x_v_WXLWtwvvLsObNlIxjXxS_g https://godbolt.org/g/Wyx2pS
【问题讨论】:
-
你必须像那样使用cryptic-SIMD吗?我可以解释发生了什么的唯一方法是通过反汇编
-
godbolt.org/g/4AaAYN 你似乎做了很多工作来以某种方式进行设置,而不是仅仅通过内存进行线性搜索,这与缓存友好度差不多。
-
@harold 我刚刚链接到它
-
@eyepatch:嗯,SSE 变体在这里看起来主要是开销。有一个单一的四元素比较指令,然后是到整数单元的转换,以及一个 shuffle/add 以获取信息以及潜在的未对齐负载。我想您已经排除了使用替代数据结构来加快搜索速度的选项?
标签: c++ performance search sse simd