AVX512-CD的使用
Usage of AVX512-CD
我目前正在与 KNL 合作,并尝试了解 AVX512 的新机遇。除了扩展寄存器方面,AVX512 还带有新的指令集。冲突检测似乎很有希望。
内在的
_mm512_conflict_epi32(...)
创建一个向量寄存器,包含给定源寄存器的无冲突子集:
如您所见,第一次出现的值会导致结果向量中相应位置出现 0。如果该值出现多次,则结果寄存器保存零扩展值。
到目前为止,一切都很好!但我想知道如何利用这个结果进行进一步的聚合或计算。我读到可以将它与前导零计数一起使用,但我认为这不足以确定子集的值。
有谁知道如何利用这一结果?
此致
现在我明白你的问题是如何利用 VPCONFLICTD/Q 的结果来构建子集以进行进一步的聚合或计算...
使用你自己的例子:
conflict_input =
[
00000001|00000001|00000001|00000001|
00000002|00000002|00000002|00000002|
00000002|00000002|00000001|00000001|
00000001|00000001|00000001|00000001
]
应用VPCONFLICTD:
__m512i out = _mm512_conflict_epi32(in);
现在我们得到:
conflict_output =
[
00000000|00000001|00000003|00000007|
00000000|00000010|00000030|00000070|
000000f0|000001f0|0000000f|0000040f|
00000c0f|00001c0f|00003c0f|00007c0f
]
bit representation =
[
................|...............1|..............11|.............111|
................|...........1....|..........11....|.........111....|
........1111....|.......11111....|............1111|.....1......1111|
....11......1111|...111......1111|..1111......1111|.11111......1111
]
如果您希望根据非重复值的首次出现获得掩码
const __m512i set1 = _mm512_set1_epi32(0xFFFFFFFF);
const __mmask16 mask = _mm512_testn_epi32_mask(out, set1);
现在您可以使用 mmask16
执行所有常用操作
[1000100000000000]
你也可以压缩它:
const __m512i out3 = _mm512_mask_compress_epi32(set0, mask, in);
[00000001|00000002|00000000|00000000|
00000000|00000000|00000000|00000000|
00000000|00000000|00000000|00000000|
00000000|00000000|00000000|00000000]
面具可以做很多事情;但是,我有趣地注意到 vplzcntd 并且不知道在哪里可以使用它:
const __m512i out1 = _mm512_conflict_epi32(in);
const __m512i out2 = _mm512_lzcnt_epi32(out1);
output2 = [
00000020|0000001f|0000001e|0000001d|
00000020|0000001b|0000001a|00000019|
00000018|00000017|0000001c|00000015|
00000014|00000013|00000012|00000011
]
= [
..........1.....|...........11111|...........1111.|...........111.1|
..........1.....|...........11.11|...........11.1.|...........11..1|
...........11...|...........1.111|...........111..|...........1.1.1|
...........1.1..|...........1..11|...........1..1.|...........1...1
]
另见一些 AVX512 直方图链接和我不久前在 中找到的信息。
我认为基本思路是将无冲突的元素集打散,然后重新聚集,重新处理,重新打散下一个无冲突的元素集。重复直到不再有冲突。
请注意,根据 vpconflictd
,重复索引的第一个出现是 "conflict-free" 元素,因此简单的重复循环会向前推进。
此过程中的步骤:
将 vpconflictd
结果转换为可以与收集指令一起使用的掩码:_mm512_testn_epi32_mask
(如@veritas 所建议)针对全一外观向量这很好,因为你需要反转它。你不能只针对它自己进行测试。
删除已经完成的元素:vpcompressd
可能对此有好处。我们甚至可以用新元素填充向量中的 "empty" 空间,这样我们就不会重新 运行 gather / process / scatter 循环,大部分元素都被屏蔽了。
例如,这个 可能 用作直方图循环,如果我这样做正确的话:
// probably slow, since it assumes conflicts and has a long loop-carried dep chain
// TOTALLY untested.
__m512i all_ones = _mm512_set1_epi32(-1); // easy to gen on the fly (vpternlogd)
__m512i indices = _mm512_loadu_si512(p);
p += 16;
// pessimistic loop that assumes conflicts
while (p < endp) {
// unmasked gather, so it can run in parallel with conflict detection
__m512i v = _mm512_i32gather_epi32(indices, base, 4);
v = _mm512_sub_epi32(gather, all_ones); // -= -1 to reuse the constant.
// scatter the no-conflict elements
__m512i conflicts = _mm512_conflict_epi32(indices);
__mmask16 knoconflict = _mm512_testn_epi32_mask(conflicts, all_ones);
_mm512_mask_i32scatter_epi32(base, knoconflict, indices, v, 4);
// if(knoconflict == 0xffff) { goto optimistic_loop; }
// keep the conflicting elements and merge in new indices to refill the vector
size_t done = _popcnt32(knoconflict);
p += done; // the elements that overlap will be replaced with the conflicts from last time
__m512i newidx = _mm512_loadu_si512(p);
// merge-mask into the bottom of the newly-loaded index vector
indices = _mm512_mask_compress_epi32(newidx, ~knoconflict, indices);
}
我们最终都需要掩码(knoconflict
和 ~knoconflict
)。最好使用 _mm512_test_epi32_mask(same,same)
并避免需要 testn
反对的向量常量。通过将掩码的反转放在 scatter
依赖链上,这可能会缩短 mask_compress 中索引的循环携带依赖链。当没有冲突时(包括迭代之间),散点是独立的。
如果冲突很少见,最好分支一下。这种无分支的冲突处理有点像在循环中使用 cmov
:它创建了一个长循环携带的依赖链。
分支预测 + 推测执行会打破这些链条,并允许多个聚集/分散同时进行。 (并且在没有冲突的情况下完全避免 运行ning popcnt
/ vpcompressd
)。
另请注意,vpconflictd
在 Skylake-avx512 上运行缓慢(但在 KNL 上则不然)。当您预计冲突非常罕见时,您甚至可以使用快速 any_conflicts()
检查,在 运行 冲突处理之前不会发现冲突的位置。
参见 for a ymm
AVX2 implementation, which should be faster than Skylake-AVX512's micro-coded vpconflictd ymm
. Expanding it to 512b zmm vectors shouldn't be difficult (and might be even more efficient if you can take advantage of AVX512 masked-compare into mask to replace a boolean operation between two compare results). Maybe with AVX512 vpcmpud k0{k1}, zmm0, zmm1
with a NEQ predicate。
我目前正在与 KNL 合作,并尝试了解 AVX512 的新机遇。除了扩展寄存器方面,AVX512 还带有新的指令集。冲突检测似乎很有希望。 内在的
_mm512_conflict_epi32(...)
创建一个向量寄存器,包含给定源寄存器的无冲突子集:
如您所见,第一次出现的值会导致结果向量中相应位置出现 0。如果该值出现多次,则结果寄存器保存零扩展值。 到目前为止,一切都很好!但我想知道如何利用这个结果进行进一步的聚合或计算。我读到可以将它与前导零计数一起使用,但我认为这不足以确定子集的值。
有谁知道如何利用这一结果?
此致
现在我明白你的问题是如何利用 VPCONFLICTD/Q 的结果来构建子集以进行进一步的聚合或计算...
使用你自己的例子:
conflict_input =
[
00000001|00000001|00000001|00000001|
00000002|00000002|00000002|00000002|
00000002|00000002|00000001|00000001|
00000001|00000001|00000001|00000001
]
应用VPCONFLICTD:
__m512i out = _mm512_conflict_epi32(in);
现在我们得到:
conflict_output =
[
00000000|00000001|00000003|00000007|
00000000|00000010|00000030|00000070|
000000f0|000001f0|0000000f|0000040f|
00000c0f|00001c0f|00003c0f|00007c0f
]
bit representation =
[
................|...............1|..............11|.............111|
................|...........1....|..........11....|.........111....|
........1111....|.......11111....|............1111|.....1......1111|
....11......1111|...111......1111|..1111......1111|.11111......1111
]
如果您希望根据非重复值的首次出现获得掩码
const __m512i set1 = _mm512_set1_epi32(0xFFFFFFFF);
const __mmask16 mask = _mm512_testn_epi32_mask(out, set1);
现在您可以使用 mmask16
执行所有常用操作[1000100000000000]
你也可以压缩它:
const __m512i out3 = _mm512_mask_compress_epi32(set0, mask, in);
[00000001|00000002|00000000|00000000|
00000000|00000000|00000000|00000000|
00000000|00000000|00000000|00000000|
00000000|00000000|00000000|00000000]
面具可以做很多事情;但是,我有趣地注意到 vplzcntd 并且不知道在哪里可以使用它:
const __m512i out1 = _mm512_conflict_epi32(in);
const __m512i out2 = _mm512_lzcnt_epi32(out1);
output2 = [
00000020|0000001f|0000001e|0000001d|
00000020|0000001b|0000001a|00000019|
00000018|00000017|0000001c|00000015|
00000014|00000013|00000012|00000011
]
= [
..........1.....|...........11111|...........1111.|...........111.1|
..........1.....|...........11.11|...........11.1.|...........11..1|
...........11...|...........1.111|...........111..|...........1.1.1|
...........1.1..|...........1..11|...........1..1.|...........1...1
]
另见一些 AVX512 直方图链接和我不久前在
我认为基本思路是将无冲突的元素集打散,然后重新聚集,重新处理,重新打散下一个无冲突的元素集。重复直到不再有冲突。
请注意,根据 vpconflictd
,重复索引的第一个出现是 "conflict-free" 元素,因此简单的重复循环会向前推进。
此过程中的步骤:
将
vpconflictd
结果转换为可以与收集指令一起使用的掩码:_mm512_testn_epi32_mask
(如@veritas 所建议)针对全一外观向量这很好,因为你需要反转它。你不能只针对它自己进行测试。删除已经完成的元素:
vpcompressd
可能对此有好处。我们甚至可以用新元素填充向量中的 "empty" 空间,这样我们就不会重新 运行 gather / process / scatter 循环,大部分元素都被屏蔽了。
例如,这个 可能 用作直方图循环,如果我这样做正确的话:
// probably slow, since it assumes conflicts and has a long loop-carried dep chain
// TOTALLY untested.
__m512i all_ones = _mm512_set1_epi32(-1); // easy to gen on the fly (vpternlogd)
__m512i indices = _mm512_loadu_si512(p);
p += 16;
// pessimistic loop that assumes conflicts
while (p < endp) {
// unmasked gather, so it can run in parallel with conflict detection
__m512i v = _mm512_i32gather_epi32(indices, base, 4);
v = _mm512_sub_epi32(gather, all_ones); // -= -1 to reuse the constant.
// scatter the no-conflict elements
__m512i conflicts = _mm512_conflict_epi32(indices);
__mmask16 knoconflict = _mm512_testn_epi32_mask(conflicts, all_ones);
_mm512_mask_i32scatter_epi32(base, knoconflict, indices, v, 4);
// if(knoconflict == 0xffff) { goto optimistic_loop; }
// keep the conflicting elements and merge in new indices to refill the vector
size_t done = _popcnt32(knoconflict);
p += done; // the elements that overlap will be replaced with the conflicts from last time
__m512i newidx = _mm512_loadu_si512(p);
// merge-mask into the bottom of the newly-loaded index vector
indices = _mm512_mask_compress_epi32(newidx, ~knoconflict, indices);
}
我们最终都需要掩码(knoconflict
和 ~knoconflict
)。最好使用 _mm512_test_epi32_mask(same,same)
并避免需要 testn
反对的向量常量。通过将掩码的反转放在 scatter
依赖链上,这可能会缩短 mask_compress 中索引的循环携带依赖链。当没有冲突时(包括迭代之间),散点是独立的。
如果冲突很少见,最好分支一下。这种无分支的冲突处理有点像在循环中使用 cmov
:它创建了一个长循环携带的依赖链。
分支预测 + 推测执行会打破这些链条,并允许多个聚集/分散同时进行。 (并且在没有冲突的情况下完全避免 运行ning popcnt
/ vpcompressd
)。
另请注意,vpconflictd
在 Skylake-avx512 上运行缓慢(但在 KNL 上则不然)。当您预计冲突非常罕见时,您甚至可以使用快速 any_conflicts()
检查,在 运行 冲突处理之前不会发现冲突的位置。
参见 ymm
AVX2 implementation, which should be faster than Skylake-AVX512's micro-coded vpconflictd ymm
. Expanding it to 512b zmm vectors shouldn't be difficult (and might be even more efficient if you can take advantage of AVX512 masked-compare into mask to replace a boolean operation between two compare results). Maybe with AVX512 vpcmpud k0{k1}, zmm0, zmm1
with a NEQ predicate。