【问题标题】:Vectorize 2d-array access (GCC)向量化二维数组访问 (GCC)
【发布时间】:2015-09-07 10:59:16
【问题描述】:

我了解矢量化的基本思想。我正在考虑将我的一个程序转换为矢量化版本。但这似乎很复杂。

有一个表(二维数组)table[M][N],以及两个向量X[1..4]Y[1..4]。我可以进行如下操作吗?有什么想法吗?

X[1..4] = table[X[1..4]][Y[1..4]]

(连续版本:X[i] = table[X[i]][Y[i]]

换句话说,下面的循环可以向量化吗?

    for(k=0; k<4; k++) {
        tmp1 = X[k];
        tmp2 = Y[k];
        X[k] = table[tmp1][tmp2];
    }

注意:X[] 始终包含不同的值

目前是用C语言实现的。

【问题讨论】:

  • 可能不会,但这取决于 X 的类型以及 M 和 N 的值(它可能只适用于较小的 M、N 值)。跨度>
  • 我不太明白同时遍历 x 和 y 维度有何意义,但也许您正在解决一些人为的数学问题。
  • 哪个向量指令集?
  • @harold 我正在使用 GCC(试用版本 4.8、4.9、5)

标签: arrays gcc vector vectorization simd


【解决方案1】:

1) 从技术上讲,hayesti 已经回答了您的问题(在 AVX2 上的 vgather 的 SSE/AVX 上的插入/水平说明将使其成为可能)。但还要注意,GCC4.9 和 ICC 都会对给定的代码 sn-p 进行矢量化(因此不需要内在函数/手动编码来实现真正的随机访问),尽管对于 GCC,您可能需要#pragma omp simd,并且您可能SSE 机器上的 ICC 还需要 -vec-threshold0。

2) 实际上,如果您必须“按原样”对给定代码进行矢量化,那么加速永远不会很好,因为您需要“摊销”vgather 或 vinsert-s 具有足够的矢量计算(您的示例中没有)以使矢量化“有利可图”。不用说您还需要适当的循环行程计数等。

我刚刚使用新的 ICC 编译器的report(或“Intel Vectorization Advisor”)输出之一检查了代码的矢量化版本的静态成本模型估计。

  • SSE 代码生成为:0.5x(即减速)
  • 对于 AVX 代码生成,它是:1.1x 加速(作为上限)
  • 对于 AVX2 代码生成,它是:1.3x - 1.4x 加速(作为上限)。

现在,请记住,所有这些加速都是由非常好的优化编译器提供的乐观上限(我不知道 GCC 是更好还是更差)。根据您的索引布局、矩阵大小和整体带宽延迟平衡以及其他一些原因 - 您通常会低于 1.4 倍,即使对于 AVX2,也很难期待显着的加速。为了使这种访问模式真正有利可图,您需要在循环体中使用 X[k] 进行一些额外的(矢量化)计算来分摊开销(而不是仅仅将数据从一个地方复制到另一个地方)。

与此同时,也有好消息。对于短期的未来 AVX-512 机器(KNL Xeon Phi,一些未来的 Xeon)vgather 性能可能会改变/提高,因此即使是简单的数据复制也可能会带来一些额外的加速,但无论如何这不是您在今天的 AVX/AVX2 机器上会观察到的东西。

最后一个小提示:如果您处理稀疏矩阵(这就是您讲述给定间接引用的原因),那么您可能会想到稀疏数据压缩行存储格式,因此会产生不同的 SIMD 权衡,尽管这与问题的原始范围太远了。

【讨论】:

    【解决方案2】:

    理论上这很好,但这取决于您拥有的处理器。您需要vector gather 功能,该功能通过 AVX2 添加到 x86 处理器,并首次出现在 Haswell 微架构中。伪代码看起来像这样

    vr1 := simd_load4(x)
    vr2 := simd_load4(y)
    vr3 := vr1 * 4; // multiply by the number of rows
    vr4 := vr3 + vr2;
    vr5 := simd_gather(base=&table, offsets=vr4)
    simd_store(x, vr5)
    

    SSE/AVX 版本可能如下所示

    __m128i vr1 = _mm_load_si128 (x);
    __m128i vr2 = _mm_load_si128 (y);
    __m128i vr3 = _mm_mul_epi32 (vr1, _mm_set1_epi32 (4));
    __m128i vr4 = _mm_add_epi32 (vr3, vr2);
    __m128i vr5 = _mm_i32gather_epi32 (table, vr4, 1);
    _mm_store_si128 (x, vr5);
    

    【讨论】:

    • 使用左移,而不是mul。速度要快得多,甚至不需要在每个元素中包含 4 的向量。
    • 如果二维数组是通过int** tabletable[i]=malloc 分配的,那我猜我们运气不好?
    【解决方案3】:

    如果要复制相邻的内存单元,可以使用 memcpy() 复制整个数据块。但是既然这里是这样,没办法,你必须使用循环。

    【讨论】:

      【解决方案4】:

      可以通过 VTBL 指令在 ARM NEON 上完成。

      NEON 可以相当快地处理最大 32 字节的 LUT。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-07-26
        • 2014-08-06
        • 2021-10-02
        • 1970-01-01
        • 2020-11-11
        • 2012-06-01
        • 1970-01-01
        相关资源
        最近更新 更多