【问题标题】:Intel Intrinsics code optimization英特尔内部代码优化
【发布时间】:2018-08-03 10:09:26
【问题描述】:

所以我试图将一个带有 short int a[101] 的常数与 intel 内在函数相乘。我已经用加法完成了,但我似乎无法弄清楚为什么它不适用于乘法。同样,在我们使用 32 位整数之前,现在我们使用 16 位短整数,因此据我所知,我们可以在内部函数中有两倍多的值来填充 128 位?

我正在尝试做的简单示例:

int main(int argc, char **argv){
    short int a[101];
    int len = sizeof(a)/sizeof(short);

    /*Populating array a with values 1 to 101*/

    mult(len, a);

    return 0;
}

int mult(int len, short int *a){
    int result = 0;
    for(int i=0; i<len; i++){
        result += a[i]*20;  
    }
    return result;
}

我的代码试图在内部函数中做同样的事情

/*Same main as before with a short int a[101] containing values 1 to 101*/

int SIMD(int len, short int *a){
    int res;
    int val[4];

    /*Setting constant value to mulitply with*/
    __m128i sum = _mm_set1_epi16(20);
    __m128i s = _mm_setzero_si128( );

    for(int i=0; i<len/4*4; i += 4){
        __m128i vec = _mm_loadu_si128((__m128i *)(a+i));
        s += _mm_mul_epu32(vec,sum);
    }

    _mm_storeu_si128((__m128i*) val, s);
    res += val[0] + val[1] + val[2] + val[3];

    /*Haldeling tail*/
    for(int i=len/4*4; i<len; i++){
        res += a[i];
    }
    return res;
}

所以我确实得到了一个数字作为结果,但该数字与天真的方法不匹配,我尝试了其他内在函数并更改数字以查看它是否有任何显着差异,但没有任何东西接近我期望的输出。计算时间也和现在的naive差不多。

【问题讨论】:

  • "s = _mm_mul_epu32(vec,sum);"看起来很奇怪。你的意思是 s += 吗?
  • 是的应该是+=。但是结果还是奇数(-1889431611)
  • res 永远不会被初始化。一个 128 位 SSE 块中有 8 个 16 位整数,而不是 4 个。所以你应该在有 4 个的地方使用 8。val 应该是 short val[8],并将表达式 val[0] + val[1] + val[2] + val[3] 扩展为 @987654328 @。 _mm_mul_epu32 乘以 32 位元素;乘以 16 位元素应该是 _mm_mullo_epi16。语句s = _mm_mul_epu32(vec,sum); 仅将vecsum 相乘,但您希望将它们相乘,然后将乘积添加到s
  • @BjornA.: s += ... 将使用 paddq(64 位元素大小),因为 GNU C 中的 __m128i 被定义为 typedef long long __m128i __attribute__((vector_size(16), may_alias))。但是,嘿,OP 已经在 16 位数据上使用 32 位元素乘法,当没有从一个元素到下一个元素的进位(溢出时)时,这个错误甚至不会导致损坏。
  • 要编写这个便携,使用_mm_add_epi16而不是GNU C / C++ +=。或者更好的是,使用 _mm_madd_epi16 (pmaddwd) 进行乘法以将水平元素对组合成 32 位总计而不会溢出,然后使用 _mm_add_epi32

标签: c optimization intel intrinsics


【解决方案1】:

一个__m128i中有8个short。所以:

for(int i=0; i<len/4*4; i += 4)

应该是

for(int i=0; i<len/8*8; i += 8)`

和:

res += val[0] + val[1] + val[2] + val[3];

应该是:

res += val[0] + val[1] + val[2] + val[3] + val[4] + val[5] + val[6] + val[7];

和:

for(int i=len/4*4; i<len; i++)

应该是:

for(int i=len/8*8; i<len; i++)

在:

s += _mm_mul_epu32(vec,sum);

_mm_mul_epu32 对 32 位元素进行操作。应该是:

s += _mm_mullo_epi16(vec, sum);

对象res未初始化;应该是:

int res = 0;

这是工作代码:

#include <stdio.h>
#include <stdlib.h>

#include <immintrin.h>

//  Number of elements in an array.
#define NumberOf(x) (sizeof (x) / sizeof *(x))


//  Compute the result with scalar arithmetic.
static int mult(int len, short int *a)
{
    int result = 0;
    for (size_t i=0; i<len; i++)
    {
        result += a[i]*20;  
    }
    return result;
}


//  Compute the result with SIMD arithmetic.
static int SIMD(int len, short int *a)
{
    //  Initialize the multiplier and the sum.
    __m128i multiplier = _mm_set1_epi16(20);
    __m128i s = _mm_setzero_si128( );

    //  Process blocks of 8 short.
    for (int i=0; i<len/8*8; i += 8)
    {
        __m128i vec = _mm_loadu_si128((__m128i *)(a+i));

        //  Multtiply by multiplier and add to sum.
        s = _mm_add_epi16(s, _mm_mullo_epi16(vec, multiplier));
    }

    //  Store the sum so far so its individual elements can be manipulated.
    short val[8];
    _mm_storeu_si128((__m128i*) val, s);

    //  Add the individual elements.
    int res = 0;
    for (size_t i = 0; i < 8; ++i)
        res += val[i];

    //  Add the elements in the tail.
    for (size_t i = len/8*8; i < len; ++i)
    {
        res += a[i];
    }

    return res;
}



int main(int argc, char **argv)
{
    short int a[96];
    int len = NumberOf(a);

    //  Initiailize a.
    for (size_t i = 0; i < len; ++i)
        a[i] = i+1;

    printf("sum by scalar arithmetic is %d.\n", mult(len, a));
    printf("sum by SIMD arithmetic is %d.\n", SIMD(len, a));

    return 0;
}

【讨论】:

  • 感谢我意识到为什么我的本地代码给出了一些非常奇怪的结果。我使用了一个过高的常量,导致溢出。但我不明白你为什么使用 a[96] 而不是 a[101]?
  • 您可以使用 32 位元素大小作为累加器,并使用 _mm_madd_epi16 (pmaddwd) 进行乘法运算,将水平元素对组合成 32 位总计而不会溢出,然后使用 @987654339 @。除非您想要 *20 包装模 2^16。那么你的水平总和只有 4 个元素。 (另外,您可以使用 SIMD shuffle / add 来完成,请参阅stackoverflow.com/questions/6996764/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-13
  • 2011-02-14
  • 1970-01-01
  • 2013-04-29
  • 2011-12-15
  • 1970-01-01
  • 2020-01-03
相关资源
最近更新 更多