【问题标题】:SSE / Optimisation - duplicating array into larger arraySSE / 优化 - 将数组复制到更大的数组中
【发布时间】:2014-10-16 16:42:21
【问题描述】:

我正在尝试优化以下功能: (基本上就是取一行 32bit Ints,将每个 int 复制到一个更大的目标数组中,然后复制每一行

for(int i = 0; i < numLines; i++)
{
    pStartOfLine  = pDest;
    for(int j = 0; j < intsPerLineSrc; j++)
    {
        *pDest = *pSrc;                     // copy pixel A to FullSizeBuffer A
        pDest++;                            // Move dest Ptr to next Pixel
        *pDest = *pSrc;                     // Copy pixel A to FullsizeBuffer AGAIN  

        pDest++;                            // Move Src and Dst Pointrs to next pixels
       pSrc++;
    }

   memcpy(pDest, pStartOfLine, (8*intsPerLineSrc) );            // Duplicate the Line written to pDest, to next line of pDest.
   pDest = pDest + (2*intsPerLineSrc);                          // move pDest to Start of Next Line
}

有效地将图像缩放至 2 * 它在两个维度上的原始大小。 现在这让我觉得这应该从 SIMD 中受益匪浅,但是我似乎找不到正确的内在指令集来帮助我在这种特定情况下。

有人愿意帮助我吗? 或者我会在这样一个简单的操作中总是受到内存限制,以至于在 SIMD 中重构是一种浪费?

是的,这部分代码最终会在多个线程中运行,因此它已经是多线程的,但我认为 SIMD 优化可能会更有帮助。

干杯,任何帮助/建议,

詹姆斯

【问题讨论】:

  • 你真的看过编译器产生了什么吗?
  • 也许我应该添加我正在将高清图像缩放为 4K 图像,并且目标缓冲区为 32Mb。我当前的代码在我当前的测试机器上大约是 3-4 毫秒,(双 nahalem xeon)现在看起来我已经接近最佳理论性能.....
  • 不,我还没有查看编译器生成的内容,是的,我应该这样做。不知道怎么做,猜猜我接下来会学习……为小费干杯。
  • 这样的声音会受到内存限制。无论如何,_mm_shuffle_epi32() 可能就是您要找的。加载 64 位、随机播放、存储 128 位。
  • 运行gcc -S &lt;normal options&gt; mycode.c,你会得到一个mycode.s

标签: c++ arrays image-processing optimization sse


【解决方案1】:

您当前的操作受内存带宽限制。

如果您能找到一种不处理整个图像而是处理块(例如 16x16 像素块到 32x32 像素块)并在每个块中进行其他计算的方法,那么您可以使您的操作减少内存带宽限制。

但是,如果您必须处理整个图像,您应该考虑一些事情来实现最大内存带宽:

  1. 对于内存带宽受限的操作,它们不随内核数量而扩展,但它们可以scale with the number of sockets。因此,如果您有双插槽系统,则内存带宽是单插槽系统的两倍(假设两个插槽都使用通常使用的相同处理器)。但是,实现两倍带宽can be tricky
  2. memcpy 函数通常未针对复制大尺寸文件进行优化。主要原因之一是它的许多实现不使用非临时存储。非临时存储的经验法则是在大小大于最慢缓存大小的两倍时使用它们。假设您的处理器具有 12 MB 三级缓存。然后,如果目标图像的大小大于 6MB,则应考虑使用非临时存储。这几乎肯定是您的情况,因为您的代码正在编写 32 MB。

这是一个如何同时使用 SSE2 和非临时存储的示例

int main() {
    int n = 16;
    int *src = (int*)_mm_malloc(n*sizeof(int),   16); //16 byte aligned 
    int *dst = (int*)_mm_malloc(2*n*sizeof(int), 16); //16 byte aligned
    for(int i=0; i<n; i++) src[i] = rand();
    for(int i=0; i<n; i+=4) {
        __m128i  x = _mm_load_si128((__m128i*)&src[i]);
        __m128i lo = _mm_shuffle_epi32(x, 0x50); // 0x50 = 1100 in base 4  
        __m128i hi = _mm_shuffle_epi32(x, 0xfa); // 0xfa = 3322 in base 4
        _mm_stream_si128((__m128i*)&dst[2*i+0], lo); //non-temporal store
        _mm_stream_si128((__m128i*)&dst[2*i+4], hi); //non-temporal store
        //_mm_store_si128((__m128i*)&dst[2*i+0], lo);
        //_mm_store_si128((__m128i*)&dst[2*i+4], hi);
    }
    //for(int i=0; i<n; i++) printf("%x ", src[i]); printf("\n");
    //for(int i=0; i<(2*n); i++) printf("%x ", dst[i]); printf("\n");
}

在您的情况下,将 n 替换为像素数。如果n 不是四的倍数,那么你必须做一些我在这里没有做的清理工作。为了做到这一点,时间存储必须是 16 字节对齐的,这就是我对齐 dst 的原因。但是,src 不必对齐 16 字节,因此您可以使用 _mm_loadu_si128 而不是对齐 src

一旦您实现了单线程的最大带宽并假设您有一个多插槽系统,您应该尝试从两个插槽中实现最大带宽。我没有足够的经验来提供帮助,但我认为可以使用numactl 来实现。示例见why-doesnt-this-code-scale-linearly

【讨论】:

  • 感谢您的评论/信息。是的,看起来该操作受带宽限制。我使用的是双插槽机器,因此可以将操作分散到多个内核上,但是正如您所提到的,工作量可能比它的价值要多。
猜你喜欢
  • 1970-01-01
  • 2017-05-20
  • 2016-05-11
  • 2018-05-31
  • 2021-04-01
  • 1970-01-01
  • 2018-09-10
  • 2022-01-12
相关资源
最近更新 更多