【问题标题】:How to vectorize the pow function (with negative bases)?如何矢量化 pow 函数(带负基)?
【发布时间】:2017-09-24 12:47:18
【问题描述】:

我正在尝试矢量化 (SSE/AVX) pow 函数。在我发现的所有实现中,它只是使用logexp 进行矢量化:

pow(x, y) = exp(y * log(x))

它适用于正数 x,但不适用于负数 x,因为负数的对数是复数。是否可以有效地矢量化 pow,同时保持处理负数 x 数字的能力?

【问题讨论】:

  • 自己写一个wrapper。
  • 你知道从负数得到复数的算法吗?如果没有,那么首先尝试找出答案。然后以非向量化的方式实现它。测试它,确保它有效。然后尝试对其进行矢量化。
  • 已知xy 是整数吗?
  • pow() 对负幂有一个复杂的结果。除了幂是整数值或无穷大的特殊情况。向量化这样的测试没有意义。
  • @AlanStokes 不,不一定,我想支持这两种情况。

标签: c++ c sse avx pow


【解决方案1】:

这是一个通用的答案,没有利用您如何实际矢量化 pow() 的任何细节。


您可以检查基向量的任何元素是否为负,并在此基础上进行分支以在快速路径和慢速路径之间进行选择。

返回实部和虚部的两个向量,因此快速路径可以为虚部返回_mm_setzero_ps()。不想要虚部的调用者可以忽略它(而不必洗牌以提取实部/虚部交替向量的实部)。

因此,仅传递非负基数的调用者获得的行为几乎与矢量化纯实数版本一样快。

但同时传递负数和非负数的调用者将获得慢版本。如果你可以对慢版本进行矢量化,那就完美了。

如果它不适用于正碱基,当有混合时,您可以同时运行并混合(基于您检查是否需要慢版本的相同比较掩码)。

对于 AVX 版本,在内部名称中键入额外的 256。 (并将检查更改为== 0xff,因为移动掩码结果中还有 4 位)。

// SSE4.1 for BLENDVPS
__m128  pow_complexresult(__m128 base, __m128 exp, __m128 &imag_result)
{
    __m128 negbase_vec = _mm_cmplt_ps(base, _mm_setzero_ps());
    unsigned negbase_mask = _mm_movemask_ps(negbase_vec);

    if (negbase_mask == 0) {               // all elements false
        imag_result = _mm_setzero_ps();
        return pow_nonegative(base, exp);   // fast path
    } else if (negbase_mask == 0xf) {      // all elements true
        return pow_negative(base, exp, imag_result);
    } else {
        // Only needed if pow_negative doesn't work for non-negative inputs.
        __m128 negpow = pow_negative(base, exp, imag_result);
        __m128 pospow = pow_simple(base, exp);
        imag_result = _mm_andn_ps(negbase_mask, imag_result);  // blend imaginary part
        return _mm_blendv_ps(pospow, negpow, negbase_vec);  // blend real part
    }
}

确保帮助函数内联,这样您就不会真正通过内存通过引用传递向量。

和/或将此包装器内联到调用者中,这可能会让检查针对常量向量进行优化。

我认为 Windows 或 System V ABI 都不会在两个 ymm 寄存器中返回两个 __m256 向量的结构,因此第二个引用 arg 可能是您将获得的最好的。

注意imag_result 是最后一个arg,所以即使在Windows x64 ABI 中,这个函数仍然可以将相同寄存器中的args 转发给pow_nonegative(base, exp);。尽管您仍然希望它内联。

【讨论】:

    【解决方案2】:

    好吧,如果你用负数做一些算术,你可以使用log()函数的主要分支,它在结果中引入一个虚构的+i*b*pi数字,然后转换为exp(+i*b*pi)因子... .. 仅在您的指数b 是一个精确整数的情况下才映射到实数.....这不是您所拥有的一般情况,因为函数pow(3) 是为double 类型声明的... . 你可以解决这个问题,编写一个包装函数来检查第一个参数是否为负,并且只有在第二个参数是奇数的情况下才会给出一个负数。实现细节被隐藏起来,作为一个练习供你完成......很容易实现一个double generalized_pow(double b, double e);,它会执行此检查并给出适当的结果。

    但是如果指数b 参数将是整数......那么你最好实现fast power algorithm,这会给你答案可能比通过exp(3)log(3) 函数更快.在 Google 中查找快速指数算法的实现。

    【讨论】:

    • 当 4 个基数和 4 个指数的 SIMD 向量可能包含多种情况时,诀窍就是 有效地 执行此操作。您可以使用_mm_cmpeq_ps(exp, _mm_round_ps(e, 0 /*nearest*/)) 检查所有指数是否已经是精确整数,如果这在您的代码中很常见,那么可能值得检查。但更有可能它只发生在编译时已知的情况下,所以你应该只写一个powi 函数,它接受一个整数向量的幂。或保证为整数的 FP 值向量。
    猜你喜欢
    • 2011-10-18
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 2015-01-18
    • 2020-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多