【问题标题】:AVX2 gather load a struct of two intsAVX2 收集加载两个整数的结构
【发布时间】:2017-06-12 20:22:36
【问题描述】:

我目前正在尝试实现我的一些现有标量代码的 AVX2 版本(Haswell CPU)。它实现了这样的步骤:

struct entry {
  uint32_t low, high;
};

// both filled with "random" data in previous loops
std::vector<entry> table;
std::vector<int>   queue;  // this is strictly increasing but
                           // without a constant delta

for (auto index : queue) {
  auto v = table[index];
  uint32_t rank = v.high + __builtin_popcount(_bzhi_u32(v.low, index % 32));
  use_rank(rank); // contains a lot of integer operations which nicely map to avx2
}

我已经用 2 个收集指令实现了这一点,每个指令都加载一个 int32,如下所示:

__m256iv_low  = _mm256_i32gather_epi32 (reinterpret_cast<int *>(table.data()) + 0, index, 8);
__m256i v_high = _mm256_i32gather_epi32 (reinterpret_cast<int *>(table.data()) + 1, index, 8);

有没有更快的方法来加载这些值?我曾考虑过使用 2 个 64 位负载(仅发出一半的读取量 => 执行端口的流量更少),然后对结果向量进行洗牌以获得 v_low 和 v_high 例如,但遗憾的是,据我所知大多数洗牌函数只允许单独洗牌 128 位。

为 Paul R 编辑: 此代码是使用我在压缩算法中使用的 Burrows Wheeler 变换的子字符串枚举例程的一部分。 table 包含位向量上的排名数据。高部分包含先前条目中的个数,而低部分被屏蔽并弹出计数,然后添加以获得给定索引前面的最终设置位数。之后会发生更多计算,幸运的是可以很好地并行化。

队列中的增量在开始和结束时都非常高(由于算法的性质)。这导致了很多缓存未命中,这也是我从 SoA 切换到 AoS 的原因,它使用移位来减少标量代码中加载端口的压力。

使用 SoA 也会产生相同的独立收集指令,但会使访问的缓存行数量增加一倍。

编辑(部分答案): 我尝试使用两个_mm_i32gather_epi64 来减少内存访问次数的一半(因此周期,请参阅here)。

__m256i index; // contains the indices
__m128i low = _mm256_extractf128_si256(index, 0);
__m128i high = _mm256_extractf128_si256(index, 1);
__m256i v_part1 = _mm256_i32gather_epi64(reinterpret_cast<long long int*>(table.data()), low , 8);
__m256i v_part2 = _mm256_i32gather_epi64(reinterpret_cast<long long int*>(table.data()), high, 8);

将我的数据加载到两个 ymm 寄存器这种格式(没有 c++):

register v_part1:
[v[0].low][v[0].high][v[1].low][v[1].high][v[2].low][v[2].high][v[3].low][v[3].high]
register v_part2:
[v[4].low][v[4].high][v[5].low][v[5].high][v[6].low][v[6].high][v[7].low][v[7].high]

是否有一种有效的方法可以将它们交错以获得原始格式:

register v_low:
[v[0].low][v[1].low][v[2].low][v[3].low][v[4].low][v[5].low][v[6].low][v[7].low]
register v_high:
[v[0].high][v[1].high][v[2].high][v[3].high][v[4].high][v[5].high][v[6].high][v[7].high]

【问题讨论】:

  • 该代码是无意义的,并且是无效的 C++。
  • @JohnZwinck:这是 AVX 内在函数。
  • 您可能想重新考虑您的 table 数据结构 - 使其成为 SoA 而不是 AoS?当然,这个决定取决于您对这些数据还做了什么,而您没有告诉我们。
  • @Christoph:感谢您的更新 - 这很有帮助。请注意,收集指令非常慢/效率低 - 使用 shuffle/permute/unpack/whatever 的解决方案,即使需要多条指令,也会更可取,但您的非连续索引很可能会破坏这个想法。
  • @Christoph: d'oh - 我刚刚意识到这会以错误的顺序交错元素 - 请忽略上述建议 - 我现在要回去睡觉了......

标签: c++ avx2


【解决方案1】:

我自己找到了一种使用 5 条指令重新排序值的方法:

// this results in [01][45][23][67] when gathering
index = _mm256_permute4x64_epi64(index, _MM_SHUFFLE(3,1,2,0));

// gather the values
__m256i v_part1 = _mm256_i32gather_epi64(i, _mm256_extractf128_si256(index, 0), 8);
__m256i v_part2 = _mm256_i32gather_epi64(i, _mm256_extractf128_si256(index, 1), 8);

// seperates low and high values
v_part1 = _mm256_shuffle_epi32(v_part1, _MM_SHUFFLE(3,1,2,0));
v_part2 = _mm256_shuffle_epi32(v_part2, _MM_SHUFFLE(3,1,2,0));

// unpack merges lows and highs: [01][23][45][56]
o1 = _mm256_unpackhi_epi64(v_part1, v_part2);
o2 = _mm256_unpacklo_epi64(v_part1, v_part2);

【讨论】:

    猜你喜欢
    • 2020-04-07
    • 2013-04-18
    • 1970-01-01
    • 2019-07-02
    • 1970-01-01
    • 1970-01-01
    • 2020-03-08
    • 2017-03-14
    • 2017-10-01
    相关资源
    最近更新 更多