AVX2基于面具打包剩下的最有效的方法是什么?

2023-11-21

如果您有一个输入数组和一个输出数组,但您只想写入那些通过特定条件的元素,那么在 AVX2 中执行此操作最有效的方法是什么?

我在 SSE 看到过这样的操作: (从:https://deplinenoise.files.wordpress.com/2015/03/gdc2015_afredriksson_simd.pdf)

__m128i LeftPack_SSSE3(__m128 mask, __m128 val)
{
 // Move 4 sign bits of mask to 4-bit integer value.
 int mask = _mm_movemask_ps(mask);
 // Select shuffle control data
 __m128i shuf_ctrl = _mm_load_si128(&shufmasks[mask]);
 // Permute to move valid values to front of SIMD register
 __m128i packed = _mm_shuffle_epi8(_mm_castps_si128(val), shuf_ctrl);
 return packed;
}

这对于 4 宽的 SSE 来说似乎很好,因此只需要 16 个条目的 LUT,但对于 8 宽的 AVX,LUT 变得相当大(256 个条目,每个 32 字节,或 8k)。

令我惊讶的是,AVX 似乎没有提供简化此过程的说明,例如带包装的蒙面商店。

我认为通过一些位改组来计算设置在左侧的符号位的数量,您可以生成必要的排列表,然后调用 _mm256_permutevar8x32_ps。但我认为这也是相当多的说明..

有谁知道使用 AVX2 执行此操作的任何技巧吗?或者说最有效的方法是什么?

以下是上述文档中左填充问题的说明:

Left.Packing.Problem


AVX2 + BMI2。请参阅我对 AVX512 的其他答案。 (更新:保存了pdep在 64 位版本中。)

我们可以用AVX2 vpermps (_mm256_permutevar8x32_ps)(或等价的整数,vpermd)进行车道交叉可变洗牌。

我们可以动态生成蒙版,自 BMI2pext(并行位提取)为我们提供了所需操作的按位版本。

当心pdep/pext are veryZen 3 之前的 AMD CPU 上速度较慢,如 Ryzen Zen 1 和 Zen 2 上的 6 uops/18 周期延迟和吞吐量。此实现在 AMD CPU 上的性能将非常糟糕。对于 AMD,您可能最好使用 128 位向量pshufb or vpermilpsLUT,或评论中讨论的一些 AVX2 可变移位建议。特别是如果您的掩码输入是矢量掩码(不是内存中已打包的位掩码)。

Zen2之前的AMD无论如何都只有128位向量执行单元,256位跨车道shuffle速度很慢。因此,128 位向量在 Zen 1 上对此非常有吸引力。但 Zen 2 有 256 位加载/存储和执行单元。 (而且微编码 pext/pdep 仍然很慢。)


对于具有 32 位或更宽元素的整数向量: 任一 1)_mm256_movemask_ps(_mm256_castsi256_ps(compare_mask)).
或者2)使用_mm256_movemask_epi8然后将第一个 PDEP 常量从 0x0101010101010101 更改为 0x0F0F0F0F0F0F0F0F 以分散 4 个连续位的块。将乘以0xFFU改为expanded_mask |= expanded_mask<<4; or expanded_mask *= 0x11;(未测试)。无论哪种方式,请使用带有 VPERMD 的随机播放掩码,而不是 VPERMPS。

对于 64 位整数或double元素,一切仍然正常;比较掩码恰好总是具有相同的 32 位元素对,因此生成的洗牌将每个 64 位元素的两半放在正确的位置。 (因此您仍然使用 VPERMPS 或 VPERMD,因为 VPERMPD 和 VPERMQ 仅适用于立即控制操作数。)

对于 16 位元素,您也许可以使用 128 位向量来适应这一点。

对于 8 位元素,请参见用于左包装字节元素的高效 sse shuffle mask 生成对于不同的技巧,将结果存储在多个可能重叠的块中。


算法:

从压缩的 3 位索引常量开始,每个位置都有自己的索引。 IE。[ 7 6 5 4 3 2 1 0 ]其中每个元素都是 3 位宽。0b111'110'101'...'010'001'000.

Use pext将我们想要的索引提取到整数寄存器底部的连续序列中。例如如果我们想要索引 0 和 2,我们的控制掩码pext应该0b000'...'111'000'111. pext将抓住010 and 000与选择器中的 1 位对齐的索引组。所选组被打包到输出的低位中,因此输出将是0b000'...'010'000。 (IE。[ ... 2 0 ])

查看注释代码了解如何生成0b111000111输入为pext来自输入向量掩码。

现在我们与压缩 LUT 处于同一条船上:解压最多 8 个压缩索引。

当你把所有的部分放在一起时,总共有三个pext/pdeps。我从我想要的方向逆向工作,所以从那个方向理解它可能也是最容易的。 (即从随机播放线开始,然后从那里向后进行。)

如果我们使用每个字节一个索引而不是打包的 3 位组,我们可以简化解包。由于我们有 8 个索引,因此只有 64 位代码才可能实现。

See 这个版本和 Godbolt Compiler Explorer 上的纯 32 位版本。我用了#ifdefs 因此它可以最佳地编译-m64 or -m32。 gcc 浪费了一些指令,但 clang 编写了非常好的代码。

#include <stdint.h>
#include <immintrin.h>

// Uses 64bit pdep / pext to save a step in unpacking.
__m256 compress256(__m256 src, unsigned int mask /* from movmskps */)
{
  uint64_t expanded_mask = _pdep_u64(mask, 0x0101010101010101);  // unpack each bit to a byte
  expanded_mask *= 0xFF;    // mask |= mask<<1 | mask<<2 | ... | mask<<7;
  // ABC... -> AAAAAAAABBBBBBBBCCCCCCCC...: replicate each bit to fill its byte

  const uint64_t identity_indices = 0x0706050403020100;    // the identity shuffle for vpermps, packed to one index per byte
  uint64_t wanted_indices = _pext_u64(identity_indices, expanded_mask);

  __m128i bytevec = _mm_cvtsi64_si128(wanted_indices);
  __m256i shufmask = _mm256_cvtepu8_epi32(bytevec);

  return _mm256_permutevar8x32_ps(src, shufmask);
}

这会编译为不从内存加载的代码,只有立即常量。 (请参阅该版本和 32 位版本的 godbolt 链接)。

    # clang 3.7.1 -std=gnu++14 -O3 -march=haswell
    mov     eax, edi                   # just to zero extend: goes away when inlining
    movabs  rcx, 72340172838076673     # The constants are hoisted after inlining into a loop
    pdep    rax, rax, rcx              # ABC       -> 0000000A0000000B....
    imul    rax, rax, 255              # 0000000A0000000B.. -> AAAAAAAABBBBBBBB..
    movabs  rcx, 506097522914230528
    pext    rax, rcx, rax
    vmovq   xmm1, rax
    vpmovzxbd       ymm1, xmm1         # 3c latency since this is lane-crossing
    vpermps ymm0, ymm1, ymm0
    ret

(后来clang像GCC一样编译,用mov/shl/sub代替imul,见下文。)

所以,根据阿格纳·福格的号码 and https://uops.info/,这是 6 uop(不计算常量,或内联时消失的零扩展 mov)。在 Intel Haswell 上,延迟为 16c(vmovq 为 1,每个 pdep/imul/pext/vpmovzx/vpermps 为 3)。不存在指令级并行性。不过,在一个循环中,这不是循环携带依赖项的一部分(就像我在 Godbolt 链接中包含的那样),瓶颈希望只是吞吐量,同时保持多次迭代。

这可能可以管理每 4 个周期一个的吞吐量,在循环中的 pdep/pext/imul 加上 popcnt 的端口 1 上出现瓶颈。当然,由于加载/存储和其他循环开销(包括比较和 movmsk),总 uop 吞吐量也很容易成为一个问题。

例如我的 Godbolt 链接中的过滤器循环是 14 uops,带有 clang,-fno-unroll-loops使其更易于阅读。如果我们幸运的话,它可能会维持每 4c 一次迭代,跟上前端的步伐。

clang 6 及更早版本创建了一个循环携带的依赖项popcnt对其输出的错误依赖,所以它的瓶颈是 3/5 的延迟compress256功能。 clang 7.0 及更高版本使用异或归零来打破错误的依赖关系(而不是仅仅使用popcnt edx,edx或者像 GCC 那样的东西:/)。

gcc(以及后来的 clang)使用多条指令进行乘以 0xFF,使用左移 8 和sub, 代替imul255。这总共需要 3 个 uops,而前端则需要 1 个,但延迟仅为 2 个周期,比 3 个周期低。(Haswell 处理mov在寄存器重命名阶段,零延迟。)最重要的是,imul只能在端口 1 上运行,与 pdep/pext/popcnt 竞争,因此最好避免该瓶颈。


由于所有支持 AVX2 的硬件也支持 BMI2,因此提供没有 BMI2 的 AVX2 版本可能没有意义。

如果您需要在很长的循环中执行此操作,如果初始缓存未命中在足够的迭代中分摊,并且仅解压 LUT 条目的开销较低,那么 LUT 可能是值得的。你还需要movmskps,因此您可以 popcnt 掩码并将其用作 LUT 索引,但您可以保存 pdep/imul/pext。

您可以使用我使用的相同整数序列来解压 LUT 条目,但是 @Froglegs 的set1() / vpsrlvd / vpand当 LUT 条目在内存中开始并且不需要首先进入整数寄存器时,可能会更好。 (32 位广播负载在 Intel CPU 上不需要 ALU uop)。然而,可变移位在 Haswell 上为 3 uops(但在 Skylake 上仅为 1)。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

AVX2基于面具打包剩下的最有效的方法是什么? 的相关文章

随机推荐

  • 如何修改 Procfile 以在 Heroku 上的非标准文件夹中运行 Gunicorn 进程?

    我是 Heroku 和 Gunicorn 的新手 所以我不确定这是如何工作的 但我做了一些搜索 我想我已经接近部署我的 Django 应用程序 1 5 1 了 所以我知道我需要一个 Procfile web gunicorn app wsg
  • 如何从墨卡托地图 (JPEG) 上的 x、y 获取纬度、经度?

    我有一个 JPEG 格式的墨卡托投影图 我想知道如何将给定的 x y 坐标与其纬度和经度联系起来 我研究过古德曼函数 但老实说我不明白如何采用该函数并应用它 即 它期望什么输入 我发现的实现 JavaScript 似乎采用 PI 和 PI
  • 过滤行为主体

    我有一个BehaviorSubject我希望能够filter 但保持其类似行为主体的质量 即新订阅者在订阅时总是会获得一个值 即使最后发出的值被过滤掉了 有没有一种简洁的方法可以使用 rxjs 的内置函数来做到这一点 例如 const is
  • 无法使用 SwiftUI 实现暗模式[重复]

    这个问题在这里已经有答案了 struct ContentView Previews PreviewProvider static var previews some View ContentView environment colorSch
  • 如何将 Qt 库添加到 Visual Studio

    我有 VC 2017 的源码 我收到错误 错误 C1083 无法打开包含文件 QtCore QMap 没有这样的文件或目录 当我尝试编译该项目时 我下载了 Qt 库并添加到 Include 项目 但问题仍然存在 我必须将 Qt 的哪个目录添
  • 在 C# 中将像素转换为英寸,反之亦然

    我希望将像素转换为英寸 反之亦然 我知道我需要 DPI 但我不确定如何获取此信息 例如 我没有Graphics对象 所以这不是一个选项 有办法吗 在视频设备上 这个问题的任何答案通常都不是很准确 要了解为什么会出现这种情况 最简单的例子是投
  • 如何在 Django Admin 中仅折叠一个字段?

    django 管理员允许您指定字段集 您正确地构建了一个将不同字段分组在一起的元组 您还可以为某些字段组指定类 其中一个类是折叠 它将把字段隐藏在可折叠区域下 这非常适合隐藏很少使用或高级的字段 以保持 UI 整洁 然而 我有一种情况 我想
  • 如果 git fetch 中途取消,它会恢复吗?

    有时获取任何 git 存储库 通过执行 git fetch repository URL 可能需要几个小时 具体取决于存储库的大小和网络速度 如果由于某种原因用户中途取消了提取 然后稍后尝试在他 她取消上次提取的完全相同的环境中提取相同的存
  • 如果声纳阈值被突破,我该如何让 Hudson/Jenkins 失败?

    我使用 Maven 构建我的 Java 应用程序 使用 Jenkins 构建 CI 使用 Sonar 构建指标 目前我有一个创建声纳报告的构建工作 通过 Jenkins 中的构建后步骤触发 我想将其设置为在满足某些阈值时构建失败 即任何重大
  • 带可选参数的 Laravel Eloquent 查询

    我正在尝试了解是否有一种简单的方法可以将可变数量的参数传递给 Eloquent 中的查询 希望使用array 据我所知 似乎没有办法在不循环的情况下做到这一点Input查看请求中设置的内容 示例如下 Laravel Eloquent 搜索两
  • 是否有一个 NumPy 函数可以返回数组中某些内容的第一个索引?

    我知道 Python 列表有一种方法可以返回某些内容的第一个索引 gt gt gt xs 1 2 3 gt gt gt xs index 2 1 NumPy 数组有类似的东西吗 是的 给定一个数组 array 和一个值 item要搜索 您可
  • 访问 Pandas 列中不规则出现的列表值的第一项

    我有一个 pandas 数据框 在其中一列中 某些值中出现了列表值 我需要能够提取列表的第一项 如果它是一个列表 如果它不是一个列表 那么该值将保持不变 我正在努力使用 lambda 函数来实现它 df1 pd DataFrame Frui
  • 在Python中获取生成器的第n项

    有没有更语法简洁的方式来编写以下内容 gen i for i in xrange 10 index 5 for i v in enumerate gen if i is index return v 发电机应该有一个似乎很自然的gen in
  • 在 Visual Studio Code 中使用建议的方法参数

    当您键入明确定义的方法时 Visual Studio Code 会建议正确的参数 有没有办法立即接管 插入它们 手动编写参数及其类型感觉不太好 类似的东西存在于javascript typescript files settings jso
  • 按名称实例化 jooq Field<>

    我正在尝试使用 jOOQ 在一些通用代码中构建 SQL 查询 我对使用 jOOQ 执行这些查询或检查结果不感兴趣 另外 这段代码是通用的 所以我不能使用 jOOQ 的代码生成 我已经设法弄清楚了这么多 List
  • Monkey 在 Python 的另一个模块中修补一个类

    我正在使用别人编写的模块 我想给猴子打补丁 init 模块中定义的类的方法 我发现的显示如何执行此操作的示例都假设我自己调用该类 例如猴子补丁 Python 类 然而 这种情况并非如此 就我而言 该类是在另一个模块的函数中初始化的 请参阅下
  • Firebase Android分页加载始终相同的项目

    我正在尝试在 firebase 中进行分页 我遵循了这种方法 Firebase 分页 limitToFirst 与 limitToLast 但我无法从 firebase 获得正确的分页项目 总是获得相同的 3 个项目 这是下面代码的问题 我
  • Html5 视频 - 在屏幕上单击播放/暂停视频

    Hi div style text align center div
  • 使用 Servlet 下载文件时如何使用 GWT?

    我正在创建一个简单的项目 该项目允许我使用 gwt 上传和下载文件 我在下载服务器上的文件时遇到问题 对于我使用的文件上传http code google com p gwtupload 并按照那里的说明进行操作 我的文件存储在网站容器外部
  • AVX2基于面具打包剩下的最有效的方法是什么?

    如果您有一个输入数组和一个输出数组 但您只想写入那些通过特定条件的元素 那么在 AVX2 中执行此操作最有效的方法是什么 我在 SSE 看到过这样的操作 从 https deplinenoise files wordpress com 20