【问题标题】:How to copy bytes from memory using pattern (YUYV packed to YUV420 planar)如何使用模式从内存中复制字节(YUYV 打包到 YUV420 平面)
【发布时间】:2015-05-14 11:45:23
【问题描述】:

让我们从这个开始:

我有一个 16 字节的内存块,我只需要将偶数字节复制到 8 字节的内存块。

我当前的算法是这样的:

unsigned int source_size = 16, destination_size = 8, i;

unsigned char * source = new unsigned char[source_size];
unsigned char * destination = new unsigned char[destination_size];

// fill source
for( i = 0; i < source_size; ++i)
{
    source[i] = 0xf + i;
}
// source :
// 0f 10 11 12  13 14 15 16  17 18 19 1a  1b 1c 1d 1e

// copy
for( i = 0; i < destination_size; ++i)
{
    destination[i] = source[i * 2];
}
// destination :
// 0f 11 13 15  17 19 1b 1d

这只是一个示例,因为我想知道当我需要获取每 3 个字节或每 4 个字节时是否有更好的方法来执行此操作,而不仅仅是偶数字节。

我知道使用循环我可以实现这一点,但我需要优化它...我不完全知道如何使用 SSE,所以我不知道在这种情况下是否可以使用,但类似 ​​memcpy 魔法有点东西会很棒。

我还考虑过使用宏来摆脱循环,因为源和目标的大小都是恒定的,但这看起来没什么大不了的。

如果我说这是为了提取 YUYV 像素格式的 YCbCr 字节,也许你可以开箱即用。另外我需要强调的是,我这样做是为了摆脱 libswscale。

【问题讨论】:

  • 反正内存带宽是有限的,何必呢?

标签: c++ memory-management video-processing micro-optimization pixelformat


【解决方案1】:

虽然我怀疑编译器和 cpu 已经在这种情况下做得很好;如果您真的想要替代品,请研究反转莫顿数字的技术。这个问题How to de-interleave bits (UnMortonizing?) 展示了如何在位上做到这一点,但这个想法也可以扩展到字节。

类似的东西(仅作为示例,这不是生产质量)

// assuming destination is already zero...
For (int i=0; i < destination_size; i += 2) {
   long* pS = (long*) &source[ i * 2 ];
   long* pD = (long*) &destination[ i ];
   long a = *pS &0xff00ff00;
   *pD |= *pS | ( *pS << 8 );
}

这是否比您的版本快取决于确切的 cpu 类型和编译器生成的内容。即测试并查看哪个更快,正如其他人提到的那样,内存获取瓶颈将掩盖给定小数组的所有内容。

【讨论】:

    【解决方案2】:

    SSSE3可以有效解决这个问题:

    #include <tmmintrin.h>  //SSSE3 and before
    ...
    //source must be 16-byte aligned
    unsigned char * source = (unsigned char *)_mm_malloc(source_size, 16);
    //destination must be 8-byte aligned (that's natural anyway)
    unsigned char * destination = (unsigned char *)_mm_malloc(destination_size, 8);
    ...
    __m128i mask = _mm_set_epi8(                        //shuffling control mask (constant)
        -1, -1, -1, -1, -1, -1, -1, -1, 14, 12, 10, 8, 6, 4, 2, 0
    );
    __m128i reg = *(const __m128i*)source;              //load 16-bit register
    __m128i comp = _mm_shuffle_epi8(reg, mask);         //do the bytes compaction
    _mm_storel_epi64((__m128i*)destination, comp);      //store lower 64 bits
    

    在生成的程序集中 (MSVC2013) 中的转换如下所示:

    movdqa  xmm0, XMMWORD PTR [rsi]
    pshufb  xmm0, XMMWORD PTR __xmm@ffffffffffffffff0e0c0a0806040200
    movq    QWORD PTR [rax], xmm0
    

    这种方法应该很快,尤其是当您进行许多此类转换时。它只花费一个混洗指令(不包括加载/存储),它似乎有1 clock latency and 0.5 clocks throughput。请注意,这种方法也可以用于其他字节模式。

    【讨论】:

    • 我必须每 16 个字节执行一次吗?无论如何感谢您的分享。
    • @RafaelLucio:是的,每输入 16 个字节执行一次操作。
    【解决方案3】:

    很遗憾,您不能仅使用 memcpy() 技巧来做到这一点。现代处理器具有 64 位寄存器,它是内存传输的最佳大小。现代编译器总是尝试优化 memcpy() 调用以一次执行 64 位(或 32 位甚至 128 位)传输。

    但在您的情况下,您需要“奇怪的”24 位或 16 位传输。这正是我们拥有 SSE、NEON 和其他处理器扩展的原因。这就是它们被广泛用于视频处理的原因。

    因此,在您的情况下,您应该使用 SSE 优化库之一或编写您自己的汇编代码来执行此内存传输。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-09-18
      • 1970-01-01
      • 2020-12-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-13
      相关资源
      最近更新 更多