【问题标题】:SSE2: How To Load Data From Non-Contiguous Memory Locations?SSE2:如何从非连续内存位置加载数据?
【发布时间】:2014-07-14 01:33:26
【问题描述】:

我正在尝试向量化一些对性能至关重要的代码。在高层次上,每次循环迭代从一个小数组中的非连续位置读取六个浮点数,然后将这些值转换为双精度并将它们添加到六个不同的双精度累加器中。这些累加器在迭代中是相同的,因此它们可以存在于寄存器中。由于算法的性质,使内存访问模式连续是不可行的。不过,该数组足够小以适合 L1 缓存,因此内存延迟/带宽不是瓶颈。

我愿意使用汇编语言或 SSE2 内在函数来并行化它。我知道我需要一次将两个浮点数加载到 XMM 寄存器的两个低位双字中,使用 cvtps2pd 将它们转换为两个双精度数,然后使用 addpd 一次将它们添加到两个累加器中。

我的问题是,如果它们在内存中不相邻,我如何将两个浮点数放入单个 XMM 寄存器的两个低位双字中?显然,任何速度太慢以至于违背并行化目的的技术都是没有用的。将不胜感激 ASM 或 Intel/GCC 内在函数的答案。

编辑:

  1. 严格来说,浮点数组的大小在编译时是未知的,但它几乎总是 256,所以这可以是特殊情况。

  2. 应读取的浮点数组元素是通过从字节数组中加载值来确定的。有六个字节数组,每个累加器一个。字节数组的读取是顺序的,每次循环迭代从每个数组读取一次,因此那里不应该有很多缓存未命中。

  3. float 数组的访问模式实际上是随机的。

【问题讨论】:

  • 您可能正在寻找 gather 指令(那里有一个完整的系列,具体取决于您的 CPU 型号。Haswell 应该有一些不错的改进)
  • @Leeor:我不认为gather 是 SSE2 指令集的一部分。不是AVX2吗? dsimcha,你的机器上有 AVX2 吗?
  • @NathanFellman,啊,对不起,我错过了这个限制
  • 从尝试_mm_set_ps_mm_set_pd 开始,看看你的编译会生成什么代码(启用优化)——它可能会让你大吃一惊。
  • 告诉我们更多关于这些浮点数在内存中的布局模式。如果它们是规则间隔的,也许交错处理是可能的?可以提供伪代码吗?

标签: performance optimization assembly sse simd


【解决方案1】:

对于这种特定情况,请查看说明参考手册中的解包和交错说明。会是这样的

movss xmm0, <addr1>
movss xmm1, <addr2>
unpcklps xmm0, xmm1

还可以查看shufps,只要您想要的数据顺序错误,它就会很方便。

【讨论】:

  • 谢谢。这似乎是最不坏的答案。不过,我得出的结论是,从中获得实际加速基本上是不可能的,因为如果没有任何其他瓶颈,矢量化将使我能够维持每时钟周期 > 1 个浮点加法的吞吐量,但是Sandy Bridge/Ivy Bridge 无论如何,每个周期最多只能执行一个浮点加载。
  • @dsimcha,这可能是最不坏的答案,因为您提供的信息不足以回答这个问题。 Agner Fog 在他的向量类库中有一个名为 lookup 的 SSE/AVX/AVX2 聚集函数,在编译时已知大小的数组的情况下使用它可以很有效。
  • @dsimcha,SB/IB 可以在每个时钟周期加载两个 128 位字。因此,每个时钟周期至少可以得到两个不连续的浮点数。
  • @Zboson:我的错。我在 AF 的表格中查到了这个。 Sandy Bridge 每个周期可以从内存执行两条 movss 指令,但只能从寄存器执行一条。这很奇怪,但你是对的。
【解决方案2】:

我认为使用Agner Fog's Vector Class Library 中的lookup 函数看看它是如何执行的会很有趣。它不是您需要编译和链接的库。它只是头文件的集合。如果将头文件放到源代码目录中,则应编译以下代码。下面的代码从六个字节数组中的每一个中一次加载 16 个字节,将它们扩展为 32 位整数(因为 lookup 函数需要这样做),然后为六个累加器中的每一个收集浮点数。您也可以将其扩展到 AVX。我不知道这是否会在性能上更好(可能会更糟)。我的猜测是,如果有一个常规模式,它可能会有所帮助(在这种情况下,gather 函数会更好)但无论如何都值得一试。

#include "vectorclass.h"    
int main() {
    const int n = 16*10;
    float x[256];
    char b[6][n];
    Vec4f sum[6];
    for(int i=0; i<6; i++) sum[i] = 0;
    for(int i=0; i<n; i+=16) {
        Vec4i in[6][4];
        for(int j=0; j<6; j++) {
            Vec16c b16 = Vec16uc().load(&b[j][i]);      
            Vec8s low,high;
            low = extend_low(b16);
            high = extend_high(b16);
            in[j][0] = extend_low(low);
            in[j][1] = extend_high(low);
            in[j][2] = extend_low(high);
            in[j][3] = extend_high(high);
        }
        for(int j=0; j<4; j++) {
            sum[0] += lookup<256>(in[0][j], x);
            sum[1] += lookup<256>(in[1][j], x);
            sum[2] += lookup<256>(in[2][j], x);
            sum[3] += lookup<256>(in[3][j], x);
            sum[4] += lookup<256>(in[4][j], x);
            sum[5] += lookup<256>(in[5][j], x);
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-02
    • 2012-08-09
    • 1970-01-01
    • 2020-09-26
    • 2012-02-29
    • 2023-03-22
    • 2011-04-27
    相关资源
    最近更新 更多