如何使用 AVX2 有效连接两个向量? (VPALIGNR 的车道交叉口版本)

2023-11-22

我已经实现了一个内联函数(_mm256_concat_epi16)。它连接两个包含 16 位值的 AVX2 向量。对于前 8 个数字效果很好。如果我想将它用于向量的其余部分,我应该更改实现。但在我的主程序中使用单个内联函数会更好。

问题是 :有没有比我更好的解决方案,或者有什么建议可以使这个内联函数更通用,适用于16 个值而不是我的解决方案8 values?我的解决方案连接了 2 个向量,但仅解决了 16 种可能状态中的 8 种状态。

**编辑*我当前对这个问题的解决方案是使用未对齐的加载函数,它可以从内存中的任何部分读取。但是,当数据在寄存器中准备好时,重用它可能会更好。然而,它可能会导致端口 5 出现瓶颈,导致出现 shuffle、permute 等问题。但吞吐量可能足够(尚未测试)。

#include <stdio.h>
#include <x86intrin.h>

inline _mm256_print_epi16(__m256i a, char* name){
    short temp[16], i;
    _mm256_storeu_si256((__m256i *) &temp[0], a);
    for(i=0; i<16; i++)
        printf("%s[%d]=%4d , ",name,i+1,temp[i]);
    printf("\n");
}

inline __m256i _mm256_concat_epi16(__m256i a, __m256i  b, const int indx){
    return _mm256_alignr_epi8(_mm256_permute2x128_si256(a,b,0x21),a,indx*2);
}

int main()
{
    __m256i a = _mm256_setr_epi16(101,102,103,104,105,106,107,108,109,1010,1011,1012,1013,1014,1015,1016);_mm256_print_epi16(a, "a");
    __m256i b = _mm256_setr_epi16(201,202,203,204,205,206,207,208,209,2010,2011,2012,2013,2014,2015,2016);_mm256_print_epi16(b, "b");

    _mm256_print_epi16(_mm256_concat_epi16(a,b,8), "c");//numbers: 0-8
    return 0;
}

输出是:

// icc  -march=native -O3 -D _GNU_SOURCE -o "concat" "concat.c"
[fedora@localhost concatination]$ "./concat"
a[1]= 101 , a[2]= 102 , a[3]= 103 , a[4]= 104 , a[5]= 105 , a[6]= 106 , a[7]= 107 , a[8]= 108 , a[9]= 109 , a[10]=1010 , a[11]=1011 , a[12]=1012 , a[13]=1013 , a[14]=1014 , a[15]=1015 , a[16]=1016 , 
b[1]= 201 , b[2]= 202 , b[3]= 203 , b[4]= 204 , b[5]= 205 , b[6]= 206 , b[7]= 207 , b[8]= 208 , b[9]= 209 , b[10]=2010 , b[11]=2011 , b[12]=2012 , b[13]=2013 , b[14]=2014 , b[15]=2015 , b[16]=2016 , 
c[1]= 109 , c[2]=1010 , c[3]=1011 , c[4]=1012 , c[5]=1013 , c[6]=1014 , c[7]=1015 , c[8]=1016 , c[9]= 201 , c[10]= 202 , c[11]= 203 , c[12]= 204 , c[13]= 205 , c[14]= 206 , c[15]= 207 , c[16]= 208 , 

对这个问题不可能给出一般性的答案。这是一个如此短的片段,最佳策略取决于周围的代码以及您正在运行的 CPU。

有时,我们可以排除那些对任何 CPU 都没有优势、只是消耗更多相同资源的事物,但在考虑未对齐负载与洗牌之间的权衡时,情况并非如此。


在可能未对齐的输入数组上的循环中,您可能最好使用未对齐的负载。特别是您的输入数组大多数时候都会在运行时对齐。如果不是,并且这是一个问题,那么如果可能的话,执行未对齐的第一个向量,然后从第一个对齐边界对齐。 IE。到达主循环对齐边界的序言的常用技巧。但对于多个指针,如果您的指针相对于彼此未对齐,通常最好对齐存储指针,并执行未对齐的加载(根据英特尔的优化手册)。 (看Agner Fog 的优化指南以及其他链接x86标记维基百科。)

在最新的 Intel CPU 上,跨越缓存行边界的矢量负载仍然具有相当好的吞吐量,但这就是您可能考虑 ALU 策略或混合洗牌和重叠负载的原因之一(在展开循环中,您可能会交替使用策略)这样你就不会在任何一个上遇到瓶颈)。


正如史蒂芬·卡农 (Stephen Canon) 在《AVX2 中的 _mm_alignr_epi8 (PALIGNR) 等效项(可能是重复的),如果您需要将多个不同的偏移窗口放入两个向量的同一串联中,那么两个存储+重复的未对齐加载非常好。在 Intel CPU 上,只要 256b 未对齐负载不跨越缓存行边界,您就可以获得每时钟 2 的吞吐量(因此alignas(64)你的缓冲区)。

不过,存储/重新加载对于单一使用情况来说并不是很好,因为对于未完全包含在任一存储中的加载,存储转发失败。它对于吞吐量来说仍然很便宜,但对于延迟来说却很昂贵。另一个巨大的优势是它的运行时变量偏移量非常高效。

如果延迟是一个问题,那么使用 ALU shuffle 可能会很好(特别是在 Intel 上,其中跨车道 shuffle 并不比车道内贵很多)。再次考虑/测量循环瓶颈,或者尝试存储/重新加载与 ALU。


洗牌策略:

您当前的函数只能在以下情况下编译:indx在编译时已知(因为palignr需要字节移位计数作为立即数)。

As @穆罕默德建议,您可以在编译时从不同的洗牌中进行选择,具体取决于indx价值。他似乎建议采用 CPP 宏,但那会很丑陋。

使用起来更加简单if(indx>=16)或类似的东西,这将优化掉。 (你可以使indx如果编译器拒绝使用明显“可变”的移位计数来编译您的代码,则为模板参数。)Agner Fog 在他的矢量类库(许可证=GPL),对于类似的功能.

有关的:使用 AVX 模拟 32 字节的移位根据轮班数,有不同的洗牌策略的答案。但它只是试图模拟换挡,而不是连续/车道交叉palignr.

vperm2i128在 Intel 主流 CPU 上速度很快(但仍然是通道交叉洗牌,因此 3c 延迟),但在 Ryzen 上速度较慢(8 uops,3c 延迟/3c 吞吐量)。如果您正在针对 Ryzen 进行调优,您需要使用if()找出一个组合vextracti128获得高车道和/或vinserti128在一条低矮的车道上。您可能还想使用单独的轮班,然后vpblendd结果在一起。


设计正确的洗牌:

The indx确定每个通道的新字节需要来自哪里。让我们通过考虑 64 位元素来简化:

 hi |  lo
D C | B A    # a
H G | F E    # b

palignr(b,a i) forms (H G D C) >> i | (F E B A) >> i
But what we want is

D C | B A    # concatq(b,a,0): no-op.  return a;

E D | C B    # concatq(b,a,1):  applies to 16-bit element counts from 1..7
          low lane needs  hi(a).lo(a)
          high lane needs lo(b).hi(a)
        return palignr(swapmerge(a,b), a, 2*i).  (Where we use vperm2i128 to lane-swap+merge hi(a) and lo(b))
F E | D C    # concatq(b,a,2)
        special case of exactly half reg width: Just use vperm2i128.
        Or on Ryzen, `vextracti128` + `vinserti128`
G F | E D    # concatq(b,a,3): applies to 16-bit element counts from 9..15
        low  lane needs lo(b).hi(a)
        high lane needs hi(b).lo(b).  vperm2i128 -> palignr looks good
        return palignr(b, swapmerge(a,b), 2*i-16).

H G | F E    # concatq(b,a,4): no op: return b;

有趣的是,lo(b) | hi(a)两者都使用palignr案例。我们永远不需要lo(a) | hi(b)作为对齐器输入。

这些设计说明直接导致此实现:

// UNTESTED
// clang refuses to compile this, but gcc works.

// in many cases won't be faster than simply using unaligned loads.
static inline __m256i lanecrossing_alignr_epi16(__m256i a, __m256i  b, unsigned int count) {
#endif
   if (count == 0)
     return a;
   else if (count <= 7)
     return _mm256_alignr_epi8(_mm256_permute2x128_si256(a,b,0x21),a,count*2);
   else if (count == 8)
      return _mm256_permute2x128_si256(a,b,0x21);
   else if (count > 8 && count <= 15)
     // clang chokes on the negative shift count even when this branch is not taken
     return _mm256_alignr_epi8(b,_mm256_permute2x128_si256(a,b,0x21),count*2 - 16);
   else if (count == 16)
     return b;
   else
     assert(0 && "out-of-bounds shift count");

// can't get this to work without C++ constexpr :/
//   else
//     static_assert(count <= 16, "out-of-bounds shift count");
}

我把它在 Godbolt 编译器资源管理器上一些测试函数将其与不同的恒定移位计数内联。 gcc6.3将其编译为

test_alignr0:
    ret            # a was already in ymm0
test_alignr3:
    vperm2i128      ymm1, ymm0, ymm1, 33   # replaces b
    vpalignr        ymm0, ymm1, ymm0, 6
    ret
test_alignr8:
    vperm2i128      ymm0, ymm0, ymm1, 33
    ret
test_alignr11:
    vperm2i128      ymm0, ymm0, ymm1, 33   # replaces a
    vpalignr        ymm0, ymm1, ymm0, 6
    ret
test_alignr16:
    vmovdqa ymm0, ymm1
    ret

哐当哽咽。首先,它说error: argument should be a value from 0 to 255为了count*2 - 16对于不使用该分支的计数if/else chain.

此外,它迫不及待地看到alignr()count 最终成为一个编译时常量:error: argument to '__builtin_ia32_palignr256' must be a constant integer,即使是在内联之后。你可以在 C++ 中通过以下方式解决这个问题count模板参数:

template<unsigned int count>
static inline __m256i lanecrossing_alignr_epi16(__m256i a, __m256i  b) {
   static_assert(count<=16, "out-of-bounds shift count");
   ...

在 C 中,您可以将其设为 CPP 宏而不是函数来处理该问题。

The count*2 - 16对于 clang 来说问题更难解决。您可以将移位计数作为宏名称的一部分,例如 CONCAT256_EPI16_7。您可能可以使用一些 CPP 技巧来分别执行 1..7 版本和 9..15 版本。 (Boost 有一些疯狂的 CPP hack。)


顺便说一句,你的打印功能很奇怪。它调用第一个元素c[1]代替c[0]。向量索引从 0 开始进行洗牌,所以这真的很令人困惑。

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

如何使用 AVX2 有效连接两个向量? (VPALIGNR 的车道交叉口版本) 的相关文章

  • 为什么这个 Web api 控制器不并发?

    我有一个 Web API 控制器 里面有以下方法 public string Tester Thread Sleep 2000 return OK 当我调用它 10 次 使用 Fiddler 时 我预计所有 10 次调用都会在大约 2 秒后
  • 如何在 VC++ CString 中验证有效的整数和浮点数

    有人可以告诉我一种有效的方法来验证 CString 对象中存在的数字是有效整数还是浮点数吗 Use tcstol http msdn microsoft com en us library w4z2wdyc aspx and tcstod
  • 在 HKCR 中创建新密钥有效,但不起作用

    我有以下代码 它返回 成功 但使用两种不同的工具使用搜索字符串 3BDAAC43 E734 11D5 93AF 00105A990292 搜索注册表不会产生任何结果 RegistryKey RK Registry ClassesRoot C
  • 尝试了解使用服务打开对话框

    我已经阅读了有关使用 mvvm 模式打开对话框的讨论 我看过几个使用服务的示例 但我不明白所有部分如何组合在一起 我发布这个问题寻求指导 以了解我应该阅读哪些内容 以更好地理解我所缺少的内容 我将在下面发布我所拥有的内容 它确实有效 但从我
  • 在 CPP 类中将 C 函数声明为友元

    我需要在 C 函数中使用类的私有变量 我正在做这样的事情 class Helper private std string name public std getName return name friend extern C void in
  • 将类对象放置在向量中?

    我注意到我可以将一个类放置在一个向量中 这是我的程序 我收到以下错误 out blackjack exe blackjack obj blackjack obj error LNK2019 unresolved external symbo
  • 前向声明类型和“已声明为类类型的非类类型”

    我对以下代码有问题 template
  • 从复选框列表中选择循环生成的复选框中的一个复选框

    抱歉我的英语不好 在我的 ASP NET 网站上 我从 SQL 表导入软件列表 看起来像这样 但实际上要长得多 Microsoft Application Error Reporting br br Microsoft Applicatio
  • 有些有助于理解“产量”

    在我不断追求少吸的过程中 我试图理解 产量 的说法 但我不断遇到同样的错误 someMethod 的主体不能是迭代器块 因为 System Collections Generic List 不是迭代器接口类型 这是我被卡住的代码 forea
  • 处理右值时的 insert 与 emplace

    std string myString std unordered set
  • RestSharp获取序列化输出

    我正在寻找一种方法来访问 AddBody 调用的序列化结果 我正在使用内置的 RestSharp 序列化器 例子 class Foo public string FooField void SendRecord var f new Foo
  • 如何在 C# Designer.cs 代码中使用常量字符串?

    如何在 designer cs 文件中引用常量字符串 一个直接的答案是在我的 cs 文件中创建一个私有字符串变量 然后编辑 Designer cs 文件以使用此变量 而不是对字符串进行硬编码 但设计者不喜欢这样抛出错误 我明白为什么这行不通
  • 在 VS 中运行时如何查看 C# 控制台程序的输出?

    我刚刚编写了一个名为 helloworld 的聪明程序 它是一个 C NET 4 5 控制台应用程序 在扭曲的嵌套逻辑迷宫深处 使用了 Console WriteLine 当我在命令行运行它时 它会运行并且我会看到输出 我可以执行其他命令并
  • 不可变类与结构

    以下是类与 C 中的结构的唯一区别 如果我错了 请纠正我 类变量是引用 而结构变量是值 因此在赋值和参数传递中复制结构的整个值 类变量是存储在堆栈上的指针 指向堆上的内存 而结构变量作为值存储在堆上 假设我有一个不可变的结构 该结构的字段一
  • 将 Word 转换为 PDF - 禁用“保存”对话框

    我有一个用 C 编写的 Word 到 PDF 转换器 除了一件事之外 它工作得很好 有时 在某些 Word 文件上 后台会出现一条消息保存源文件中的更改 gt 是 否 取消 但我没有对源文件进行任何更改 我只想从 Word 文件创建 PDF
  • 如何最好地以编程方式将 `__attribute__ ((unused))` 应用于这些自动生成的对象?

    In my makefile我有以下目标 它将文本 HTML 资源 编译 为unsigned char数组使用xxd i http linuxcommand org man pages xxd1 html 我将结果包装在匿名命名空间和标头保
  • 代码中的.net Access Forms身份验证“超时”值

    我正在向我的应用程序添加注销过期警报 并希望从我的代码访问我的 web config 表单身份验证 超时 值 我有什么办法可以做到这一点吗 我认为您可以从 FormsAuthentication 静态类方法中读取它 这比直接读取 web c
  • 没有“对 *this”功能的右值引用的解决方法

    我有一个围绕可移动对象的代理容器类 并希望代理能够隐式生成对底层对象的右值引用 但仅当代理本身被移动时 我相信我将能够按照提案 n2439 实施此行为 将移动语义扩展到 this http www open std org jtc1 sc2
  • 如何在 sql azure 上运行 aspnet_regsql? [复制]

    这个问题在这里已经有答案了 可能的重复 将 ASP NET 成员资格数据库迁移到 SQL Azure https stackoverflow com questions 10140774 migrating asp net membersh
  • 是否允许全局静态标识符以单个 _ 开头?

    换句话说 可能static 文件范围 全局变量恰好以一个下划线开头 而不会产生与 C 实现发生名称冲突的可能性 https www gnu org software libc manual html node Reserved Names

随机推荐