在 AVX 寄存器内循环字节的有效方法

2024-01-20

摘要/tl;博士:除了进行 2 倍移位并将结果混合在一起之外,还有什么方法可以按位旋转 YMM 寄存器中的字节(使用 AVX)?

对于 YMM 寄存器中的每 8 个字节,我需要向左旋转 7 个字节。每个字节都需要比前一个字节向左旋转一位。因此,第 1 个字节应旋转 0 位,第七个字节应旋转 6 位。

目前,我已经实现了一个实现,通过[我在这里使用 1 位循环作为示例]将寄存器分别向左移动 1 位,向右移动 7 位。然后,我使用混合操作(固有操作 _mm256_blend_epi16)从第一个和第二个临时结果中选择正确的位,以获得最终的旋转字节。
每个字节总共需要 2 次移位操作和 1 次混合操作,并且需要旋转 6 个字节,因此每个字节需要 18 次操作(移位和混合的性能几乎相同)。

一定有比使用 18 次操作来旋转单个字节更快的方法来做到这一点!

此外,我需要随后在新寄存器中组装所有字节。我通过将带有“set”指令的 7 个掩码加载到寄存器中来实现此目的,这样我就可以从每个寄存器中提取正确的字节。我将这些掩码与寄存器进行“与”运算,以从中提取正确的字节。然后,我将单字节寄存器异或在一起,以获得包含所有字节的新寄存器。 这总共需要 7+7+6 次操作,因此另外 20 次操作(每个寄存器)。

我可以使用提取内在函数 (_mm256_extract_epi8) 来获取单个字节,然后使用 _mm256_set_epi8 来组装新寄存器,但我还不知道这是否会更快。 (英特尔内在函数指南中没有列出这些函数的性能,所以也许我在这里误解了一些东西。)

这使得每个寄存器总共有 38 次操作,对于在寄存器内以不同方式旋转 6 个字节来说,这似乎不是最佳选择。

我希望更精通 AVX/SIMD 的人可以在这里指导我 - 无论我是否以错误的方式进行此操作 - 因为我觉得我现在可能正在这样做。


The XOP指令集 https://en.wikipedia.org/wiki/XOP_instruction_set确实提供_mm_rot_epi8() https://msdn.microsoft.com/en-us/library/gg445129%28v=vs.100%29.aspx(这不是 Microsoft 特有的;从 4.4 或更早版本开始,它也可以在 GCC 中使用,并且在最近的 clang 中也应该可以使用)。它可用于以 128 位为单位执行所需的任务。不幸的是,我没有支持 XOP 的 CPU,所以我无法测试它。

在 AVX2 上,将 256 位寄存器分成两半,一半包含偶数字节,另一半包含奇数字节右移 8 位,从而允许 16 位向量乘法来实现这一目的。给定常量(使用 GCC 64 位组件数组格式)

static const __m256i epi16_highbyte = { 0xFF00FF00FF00FF00ULL,
                                        0xFF00FF00FF00FF00ULL,
                                        0xFF00FF00FF00FF00ULL,
                                        0xFF00FF00FF00FF00ULL };
static const __m256i epi16_lowbyte  = { 0x00FF00FF00FF00FFULL,
                                        0x00FF00FF00FF00FFULL,
                                        0x00FF00FF00FF00FFULL,
                                        0x00FF00FF00FF00FFULL };
static const __m256i epi16_oddmuls  = { 0x4040101004040101ULL,
                                        0x4040101004040101ULL,
                                        0x4040101004040101ULL,
                                        0x4040101004040101ULL };
static const __m256i epi16_evenmuls = { 0x8080202008080202ULL,
                                        0x8080202008080202ULL,
                                        0x8080202008080202ULL,
                                        0x8080202008080202ULL };

旋转操作可以写成

__m256i byteshift(__m256i value)
{
    return _mm256_or_si256(_mm256_srli_epi16(_mm256_mullo_epi16(_mm256_and_si256(value, epi16_lowbyte), epi16_oddmuls), 8),
                           _mm256_and_si256(_mm256_mullo_epi16(_mm256_and_si256(_mm256_srai_epi16(value, 8), epi16_lowbyte), epi16_evenmuls), epi16_highbyte));
}

经验证,使用 GCC-4.8.4 在 Intel Core i5-4200U 上可以产生正确的结果。例如,输入向量(作为单个 256 位十六进制数)

88 87 86 85 84 83 82 81 38 37 36 35 34 33 32 31 28 27 26 25 24 23 22 21 FF FE FD FC FB FA F9 F8

被旋转为

44 E1 D0 58 24 0E 05 81 1C CD C6 53 A1 CC 64 31 14 C9 C4 52 21 8C 44 21 FF BF BF CF DF EB F3 F8

其中最左边的八位字节左移 7 位,接下来的 6 位,依此类推;对于所有 32 个八位位组,第七个八位位组不变,第八个八位位组旋转 7 位,依此类推。

我不确定上述函数定义是否编译为最佳机器代码(这取决于编译器),但我当然对其性能感到满意。

由于您可能不喜欢上述函数的简洁格式,因此这里采用过程式扩展形式:

static __m256i byteshift(__m256i value)
{
    __m256i low, high;
    high = _mm256_srai_epi16(value, 8);
    low = _mm256_and_si256(value, epi16_lowbyte);
    high = _mm256_and_si256(high, epi16_lowbyte);
    low = _mm256_mullo_epi16(low, epi16_lowmuls);
    high = _mm256_mullo_epi16(high, epi16_highmuls);
    low = _mm256_srli_epi16(low, 8);
    high = _mm256_and_si256(high, epi16_highbyte);
    return _mm256_or_si256(low, high);
}

在评论中,彼得·科德斯 https://stackoverflow.com/users/224132/peter-cordes建议更换srai+andsrli,并且可能是最后的and+or with a blendv。前者很有意义,因为它纯粹是一种优化,但后者可能(但在当前的英特尔 CPU 上!)实际上更快。

我尝试了一些微基准测试,但无法获得可靠的结果。我通常在 x86-64 上使用 TSC,并使用存储到数组的输入和输出进行数十万次测试的中值。

我认为如果我在这里列出变体是最有用的,因此任何需要此类功能的用户都可以对其实际工作负载进行一些基准测试,并测试是否存在任何可测量的差异。

我也同意他的建议使用odd and even代替high and low,但请注意,由于向量中的第一个元素编号为 0,因此第一个元素是even, 第二odd, 等等。

#include <immintrin.h>

static const __m256i epi16_oddmask  = { 0xFF00FF00FF00FF00ULL,
                                        0xFF00FF00FF00FF00ULL,
                                        0xFF00FF00FF00FF00ULL,
                                        0xFF00FF00FF00FF00ULL };
static const __m256i epi16_evenmask = { 0x00FF00FF00FF00FFULL,
                                        0x00FF00FF00FF00FFULL,
                                        0x00FF00FF00FF00FFULL,
                                        0x00FF00FF00FF00FFULL };
static const __m256i epi16_evenmuls = { 0x4040101004040101ULL,
                                        0x4040101004040101ULL,
                                        0x4040101004040101ULL,
                                        0x4040101004040101ULL };
static const __m256i epi16_oddmuls  = { 0x8080202008080202ULL,
                                        0x8080202008080202ULL,
                                        0x8080202008080202ULL,
                                        0x8080202008080202ULL };

/* Original version suggested by Nominal Animal. */
__m256i original(__m256i value)
{
    return _mm256_or_si256(_mm256_srli_epi16(_mm256_mullo_epi16(_mm256_and_si256(value, epi16_evenmask), epi16_evenmuls), 8),
                           _mm256_and_si256(_mm256_mullo_epi16(_mm256_and_si256(_mm256_srai_epi16(value, 8), epi16_evenmask), epi16_oddmuls), epi16_oddmask));
}

/* Optimized as suggested by Peter Cordes, without blendv */
__m256i no_blendv(__m256i value)
{
    return _mm256_or_si256(_mm256_srli_epi16(_mm256_mullo_epi16(_mm256_and_si256(value, epi16_evenmask), epi16_evenmuls), 8),
                           _mm256_and_si256(_mm256_mullo_epi16(_mm256_srli_epi16(value, 8), epi16_oddmuls), epi16_oddmask));
}

/* Optimized as suggested by Peter Cordes, with blendv.
 * This is the recommended version. */
__m256i optimized(__m256i value)
{
    return _mm256_blendv_epi8(_mm256_srli_epi16(_mm256_mullo_epi16(_mm256_and_si256(value, epi16_evenmask), epi16_evenmuls), 8),
                              _mm256_mullo_epi16(_mm256_srli_epi16(value, 8), epi16_oddmuls), epi16_oddmask);
}

以下是以显示各个操作的方式编写的相同函数。虽然它根本不影响理智的编译器,但我已经标记了函数参数和每个临时值const,因此很明显如何将每个插入到后续表达式中,以将函数简化为上述简洁形式。

__m256i original_verbose(const __m256i value)
{
    const __m256i odd1  = _mm256_srai_epi16(value, 8);
    const __m256i even1 = _mm256_and_si256(value, epi16_evenmask);
    const __m256i odd2  = _mm256_and_si256(odd1, epi16_evenmask);
    const __m256i even2 = _mm256_mullo_epi16(even1, epi16_evenmuls);
    const __m256i odd3  = _mm256_mullo_epi16(odd3, epi16_oddmuls);
    const __m256i even3 = _mm256_srli_epi16(even3, 8);
    const __m256i odd4  = _mm256_and_si256(odd3, epi16_oddmask);
    return _mm256_or_si256(even3, odd4);
}

__m256i no_blendv_verbose(const __m256i value)
{
    const __m256i even1 = _mm256_and_si256(value, epi16_evenmask);
    const __m256i odd1  = _mm256_srli_epi16(value, 8);
    const __m256i even2 = _mm256_mullo_epi16(even1, epi16_evenmuls);
    const __m256i odd2  = _mm256_mullo_epi16(odd1, epi16_oddmuls);
    const __m256i even3 = _mm256_srli_epi16(even2, 8);
    const __m256i odd3  = _mm256_and_si256(odd2, epi16_oddmask);
    return _mm256_or_si256(even3, odd3);
}

__m256i optimized_verbose(const __m256i value)
{
    const __m256i even1 = _mm256_and_si256(value, epi16_evenmask);
    const __m256i odd1  = _mm256_srli_epi16(value, 8);
    const __m256i even2 = _mm256_mullo_epi16(even1, epi16_evenmuls);
    const __m256i odd2  = _mm256_mullo_epi16(odd1, epi16_oddmuls);
    const __m256i even3 = _mm256_srli_epi16(even2, 8);
    return _mm256_blendv_epi8(even3, odd2, epi16_oddmask);
}

我个人确实最初以上述详细形式编写我的测试函数,因为形成简洁版本是一组简单的复制粘贴。然而,我确实测试了这两个版本,以验证是否引入任何错误,并保持详细版本可访问(作为注释等),因为简洁版本基本上是只写的。编辑详细版本,然后将其简化为简洁形式,比尝试编辑简洁版本要容易得多。

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

在 AVX 寄存器内循环字节的有效方法 的相关文章

随机推荐

  • 错误:无法加载文件或程序集“Oracle.ManagedDataAccessDTC”或其依赖项之一

    背景 我有一个包含控制台项目和 MVC4 Web 应用程序的解决方案 两者都引用 Oracle ManagedDataAccess 托管 ODP NET 数据访问提供程序 引用是同一文件 两者均未引用 Oracle ManagedDataA
  • 尝试在 psql 中使用“\i [文件名]”,得到“无效参数”

    使用 Windows 10 Postgres 11 我有一个文件C Users myname some path query sql包含一个典型的 SELECT 查询 select a id m toagentid m maxstart f
  • Docker for Windows - 访问本地网络中的容器

    我已经安装了适用于 Windows 的 Docker 并在其上运行 Nexus Repository Manager 容器 现在我想让我的 Nexus 容器可以从内部网络中的其他电脑访问 怎么做 您必须将端口映射到容器 端口 443 的示例
  • Meteor:ReferenceError:帐户未定义

    我刚刚完成了 Meteor 包的开发 现在我想通过将其添加到新的 Meteor 应用程序来测试它 my cool package name package js Package on use function api api use ema
  • 服务器已经在运行。检查…/tmp/pids/server.pid。退出 - 轨道

    rails s gt Booting WEBrick gt Rails 4 0 4 application starting in development on http 0 0 0 0 3000 gt Run rails server h
  • 带参数的 RelayCommand 抛出 MethodAccessException

    我正在使用 Net 和 MVVM Light 创建应用程序 但 RelayCommands 遇到一些问题 我正在尝试创建一个 RelayCommand 它接受一个参数并将其传递给同一 ViewModel 中的函数 然而 每次我尝试这样做时
  • grails 3 中的外部属性文件

    我需要从 grails 3 中的外部文件属性读取配置 在 grails 2 x 中 我将文件链接到 grails config locations classpath config properties 在config groovy中 但是
  • 如何以这种特定方式拆分 git commit

    情况 我有一个git提交 在 HEAD 处 其中混合了额外的日志记录代码 然后是一些 真实代码 现在我想做以下事情 编辑掉所有日志代码 基本上清理了代码库 Commit this 我现在在最后两次提交中拥有所需的状态 但它们的顺序错误 首先
  • 用于按分隔符分割字符串的 mySQL 存储过程

    我正在编写一个存储过程 它将传递的字符串分解为 传递分隔符并返回结果的第 n 个元素 n 已通过 也 所以这就是我想出的 CREATE PROCEDURE SPLIT IN strToSplit text IN strDelimiter v
  • 调整具有宽度限制的框架大小

    我有简单的形式TForm1有 2 个面板 首先与Align alLeft第二个是Align alClient和空框TFrame1 当我将以下过程添加到表单中时 一切正常 procedure TForm1 FormCreate Sender
  • 禁用链接以停止 JQuery 中的双击

    我如何禁用所有链接button点击一次后上课 我希望能够在一个地方完成此操作 而不必单独更改所有这些 有什么想法吗 到目前为止我得到了这个 a button click function this attr disabled disable
  • 使用指向非静态成员函数的指针实现回调

    假设我正在开发一个杂货清单管理器 我有一扇窗户 上面有GroceryListDisplay 这是一个显示购物清单上的商品的控件 杂货数据由程序的模型组件存储在GroceryStorage class 要将保存的文件加载到我的程序中 必须使用
  • Flutter SharedPreferences如何加载所有保存的?

    如何加载 SharedPreferences 中保存的所有内容 我保存了很多布尔值 需要将所有布尔值加载到列表中 这就是我保存的方式 SharedPreferences sharedPreferences bool isfavorit ov
  • T-SQL 分割字符串

    我有一个 SQL Server 2008 R2 列 其中包含一个需要用逗号分隔的字符串 我在 StackOverflow 上看到了很多答案 但没有一个在 R2 中有效 我已确保我对任何拆分函数示例具有选择权限 非常感谢任何帮助 我之前用过这
  • R中Box Cox变换故障排除(需要使用for循环或apply)

    请在下面找到我的数据 行是疾病组 0 对照 1 溃疡性结肠炎和 2 克罗恩病 列是基因表达值 structure c 5 54312e 05 5 6112e 06 9 74312e 05 1 3612e 06 1 29312e 05 7 2
  • R 中 nlme 线性混合模型中相互作用显着性的检验

    I use lme函数在nlme用于测试因子水平的 R 包items与因子水平有显着的交互作用condition 因素condition有两个级别 Control and Treatment 以及因子items有 3 个级别 E1 E3 我
  • 如何在ubuntu-18.04上安装nexus

    我需要帮助在 ubuntu 18 04 上安装 nexus oss 我在互联网上找不到任何 apt get 命令 我尝试在 sudo apt get search nexus 中搜索nexus包 但无法获得正确的nexus版本包 我在网上浏
  • Bootstrap Affix 插件内存泄漏

    这些行 https github com twbs bootstrap blob master js affix js L19 L21在引导程序词缀插件中似乎会导致内存泄漏 因为窗口获取对从未释放的词缀实例的引用 作为解决方法 我使用此代码
  • OpenAPI 生成器的 Gradle 配置

    当将 OpenAPI 生成器与 Gradle 一起使用时 我希望将性别源发送到其他源生成器插件使用的标准目录 类似 Maven 生成源的东西 到目前为止 我还无法做到这一点 特别是限制生成 Java 源类而不是整个 原型项目 看来 Open
  • 在 AVX 寄存器内循环字节的有效方法

    摘要 tl 博士 除了进行 2 倍移位并将结果混合在一起之外 还有什么方法可以按位旋转 YMM 寄存器中的字节 使用 AVX 对于 YMM 寄存器中的每 8 个字节 我需要向左旋转 7 个字节 每个字节都需要比前一个字节向左旋转一位 因此