将8位整数放入16位整数数组

时间:2016-02-28 19:35:39

标签: c++ c++11 optimization

我正在开发一个处理图像的程序。如果我可以将RGBA值存储在16位整数中,我可以通过使用SSE来提高性能(没有溢出的风险)。然而,从8位整数到16位整数的转换是瓶颈。将带符号的8位整数放入16位整数数组的最快方法是什么,效率相当于

int8_t a[128];
int16_t b[128];

for (int i=0;i<128;i++)
       b[i]=a[i];

我正在使用openmp和指针。

2 个答案:

答案 0 :(得分:2)

Clang将使用-O2

对此代码进行矢量化
#include <cstdlib>
#include <cstdint>
#include <cstdio>

const int size = 128;
uint8_t a[size];
int16_t b[size];


static __inline__ unsigned long long rdtsc(void)
{
    unsigned hi, lo;
    __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
    return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}


void convert(uint8_t* src, int16_t* dest)
{
    for (int i=0;i<size;i++)
    dest[i]=src[i];
}

int main()
{
    int sum1 = 0;
    int sum2 = 0;
    for(int i = 0; i < size; i++)
    {
        a[i] = rand();
        sum1 += a[i];
    }
    auto t = rdtsc();
    convert(a, b);
    t = rdtsc() - t;
    for(int i = 0; i < size; i++)
    {
        sum2 += b[i];
    }

    printf("%d = %d\n", sum1, sum2);
    printf("t=%llu\n", t);
}

这是clang ++生成的代码。

; The loop inlined from `convert` as a single pass. 
    #APP
    rdtsc
    #NO_APP
    movl    %eax, %esi
    movl    %edx, %ecx
    movq    a(%rip), %xmm1
    movq    a+8(%rip), %xmm2
    pxor    %xmm0, %xmm0
    punpcklbw   %xmm0, %xmm1
    punpcklbw   %xmm0, %xmm2
    movdqa  %xmm1, b(%rip)
    movdqa  %xmm2, b+16(%rip)
    movq    a+16(%rip), %xmm1
    movq    a+24(%rip), %xmm2
    punpcklbw   %xmm0, %xmm1
    punpcklbw   %xmm0, %xmm2
    movdqa  %xmm1, b+32(%rip)
    movdqa  %xmm2, b+48(%rip)
    movq    a+32(%rip), %xmm1
    movq    a+40(%rip), %xmm2
    punpcklbw   %xmm0, %xmm1
    punpcklbw   %xmm0, %xmm2
    movdqa  %xmm1, b+64(%rip)
    movdqa  %xmm2, b+80(%rip)
    movq    a+48(%rip), %xmm1
    movq    a+56(%rip), %xmm2
    punpcklbw   %xmm0, %xmm1
    punpcklbw   %xmm0, %xmm2
    movdqa  %xmm1, b+96(%rip)
    movdqa  %xmm2, b+112(%rip)
    movq    a+64(%rip), %xmm1
    movq    a+72(%rip), %xmm2
    punpcklbw   %xmm0, %xmm1
    punpcklbw   %xmm0, %xmm2
    movdqa  %xmm1, b+128(%rip)
    movdqa  %xmm2, b+144(%rip)
    movq    a+80(%rip), %xmm1
    movq    a+88(%rip), %xmm2
    punpcklbw   %xmm0, %xmm1
    punpcklbw   %xmm0, %xmm2
    movdqa  %xmm1, b+160(%rip)
    movdqa  %xmm2, b+176(%rip)
    movq    a+96(%rip), %xmm1
    movq    a+104(%rip), %xmm2
    punpcklbw   %xmm0, %xmm1
    punpcklbw   %xmm0, %xmm2
    movdqa  %xmm1, b+192(%rip)
    movdqa  %xmm2, b+208(%rip)
    movq    a+112(%rip), %xmm1
    movq    a+120(%rip), %xmm2
    punpcklbw   %xmm0, %xmm1
    punpcklbw   %xmm0, %xmm2
    movdqa  %xmm1, b+224(%rip)
    movdqa  %xmm2, b+240(%rip)
    #APP
    rdtsc
    #NO_APP

对于较大的尺寸,它需要更多,因为编译器不会内联到无限大小。

gcc只能在没有-O3的其他选项的情况下进行矢量化,但它会生成类似的代码。

但是如果使用-ftree-vectorize,gcc也会在-O2中生成SSE指令。

答案 1 :(得分:1)

我做了一些测量,并在我的(相当嘈杂的)桌面上,它运行3.1Ghz AMD CPU。我对AMD的缓存策略并不太熟悉,但对此而言,这并不重要。

以下是代码:gist of test.cpp 我用-O2用GCC 4.92

编译它

结果:

original: 0.0905usec
aligned64: 0.1191usec
unrolled_8s: 0.0625usec
unrolled_64s: 0.0497usec
  • 原创 - 您的原始代码
  • aligned64 - 我想也许对齐是一个问题,所以我强迫它进入* 64位对齐。这不是问题。
  • unrolled_8s - 将128循环展开为八组。
  • unrolled_64s - 将128循环展开为64个组。

我的CPU运行在3.1Ghz CPU,所以我们假设它每秒大约有30亿个周期,所以每纳秒大约需要3个周期。

  • 原始:90纳秒~270个周期。因此(270/128)=每份2.11个循环
  • align64:119 nsec~357个周期。因此(357/128)=每份2.79个周期
  • unrolled_8s:62 nsec~186个周期。因此(186/128)= 每份1.45周期
  • unrolled_64s:50纳秒~150个周期。因此(267/128)= 每份1.17个周期

请不要盲目地假设展开你的循环会更好!我滥用了两件事,在这里作弊很多:

  • 所有数据都保留在缓存中
  • 所有说明(代码)都保留在缓存中

如果所有数据都从CPU的缓存中失效,那么从主内存中重新获取数据可能会付出可怕的代价。在最坏的情况下,执行复制的线程可能会在每个副本之间被CPU(“上下文切换”)抛出。最重要的是,数据可能会从缓存中失效。这意味着每个上下文切换需要支付数百微秒,每个内存访问需要数百个周期。