如何将 6 字节无符号整数快速复制到内存区域



我需要将一个 6 字节的整数值复制到内存区域中,从它的开头开始并尽快。如果硬件支持这样的操作,我想使用它(我现在使用 x64 处理器,编译器是 GCC 4.6.3(。

memset不适合该作业,因为它只能复制字节。std::fill也不是很好,因为我甚至无法定义迭代器,在内存区域中的 6 个字节宽度的位置之间跳转。

所以,我想要一个函数:

void myMemset(void* ptr, uint64_t value, uint8_t width, size_t num)

这看起来像memset,但width还有一个额外的参数来定义要复制的value中的字节数。如果这样的东西可以用C++表达,那就更好了。

我已经知道明显的myMemset实现,它会调用循环memcpy,最后一个参数(要复制的字节(等于width。我也知道,我可以定义一个大小为 6 * 8 = 48 字节的临时内存区域,用 6 字节整数填充它,然后将其memcpy到目标区域。

我们能做得更好吗?

@Mark赎金评论

:复制 6

个字节,然后复制 6、12、24、48、96 等。

void memcpy6(void *dest, const void *src, size_t n /* number of 6 byte blocks */) {
  if (n-- == 0) {
    return;
  }
  memcpy(dest, src, 6);
  size_t width = 1;
  while (n >= width) {
    memcpy(&((char *) dest)[width * 6], dest, width * 6);
    n -= width;
    width <<= 1; // double w
  }
  if (n > 0) {
    memcpy(&((char *) dest)[width * 6], dest, n * 6);
  }
}

优化:nwidth比例缩小6。

[编辑]
更正的目标@SchighSchagh
新增演员(char *)

确定 CPU 支持的最有效写入大小;然后找到可以平均除以 6 和该写入大小的最小数字,并调用该"块大小"。

现在将内存区域拆分为该大小的块。每个块将是相同的,所有写入都将正确对齐(假设内存区域本身已正确对齐(。

例如,如果 CPU 支持的最有效写入大小为 4 字节(例如古代 80486(,则"块大小"将为 12 字节。您将设置 3 个通用寄存器,每个块执行 3 个存储。

再举一个例子,如果CPU支持的最有效写入大小是16字节(例如SSE(,那么"块的大小"将是48字节。您将设置 3 个 SSE 寄存器,每个块执行 3 个存储。

此外,我建议将内存区域的大小向上舍入,以确保它是块大小的倍数(带有一些"非严格必要"的填充(。一些不必要的写入比填充"部分块"的代码成本更低。

第二种最有效的方法可能是使用内存副本(但不是memcpy()memmove()(。在这种情况下,您将写入最初的 6 个字节(或 12 字节或 48 字节或其他字节(,然后从(例如(&area[0]复制到&area[6](从最低到最高(,直到到达末尾。为此memmove()将不起作用,因为它会注意到该区域重叠并从最高到最低工作;并且memcpy()将不起作用,因为它假定源和目标不重叠;因此,您必须创建自己的内存副本以适应。这样做的主要问题是内存访问次数增加了一倍 - "读取和写入"比"单独写入"慢。

如果您的Num足够大,您可以尝试使用 AVX 矢量指令,该指令一次将处理 32 个字节(_mm256_load_si256/_mm256_store_si256 或其未对齐的变体(。

由于 32

不是 6 的倍数,因此您必须首先使用短 memcpy 或 32/64 位移动将 6 字节模式复制 16 次。

ABCDEF
ABCDEF|ABCDEF
ABCD EFAB CDEF|ABCD EFAB CDEF
ABCDEFAB CDEFABCD EFABCDEF|ABCDEFAB CDEFABCD EFABCDE
ABCDEFABCDEFABCD EFABCDEFABCDEFAB CDEFABCDEFABCDEF|ABCDEFABCDEFABCD EFABCDEFABCDEFAB CDEFABCDEFABCDEF

您还将以简短的记忆结束。

尝试__movsq内部函数(仅限 x64;在汇编中,rep movsq(,一次移动 8 个字节,具有合适的重复因子,并将目标地址设置为源后 6 个字节。检查重叠地址是否得到巧妙处理。

一次

写入 8 个字节。

在 64 位机器上,生成的代码当然可以在 8 字节写入中很好地运行。 在处理了一些设置问题后,在一个紧密循环中,每次写入大约 num 次写入 8 个字节。 假设适用 - 请参阅代码。

// assume little endian
void myMemset(void* ptr, uint64_t value, uint8_t width, size_t num) {
  assert(width > 0 && width <= 8);
  uint64_t *ptr64 = (uint64_t *) ptr;
  // # to stop early to prevent writing past array end
  static const unsigned stop_early[8 + 1] = { 0, 8, 3, 2, 1, 1, 1, 1, 0 };
  size_t se = stop_early[width];
  if (num > se) {
    num -= se;
    // assume no bus-fault with 64-bit write @ `ptr64, ptr64+1, ... ptr64+7`
    while (num > 0) { // tight loop
      num--;
      *ptr64 = value;
      ptr64 = (uint64_t *) ((char *) ptr64 + width);
    }
    ptr = ptr64;
    num = se;
  }
  // Cope with last few writes
  while (num-- > 0) {
    memcpy(ptr, &value, width);
    ptr = (char *) ptr + width;
  }
}

进一步的优化包括一次写入 2 个块width == 3 or 4width == 2时一次写入 4 个块,一次写入 8 个块width == 1

最新更新