Memcpy 实现中的优化

Abh*_*bhi 5 c optimization

下面是我在搜索优化memcpy实现时得到的代码。
这是链接

void *memcpy(void *dst, void const *src, size_t len) {
    long *plDst = (long *)dst;
    long const *plSrc = (long const *)src;

    if (!(src & 0xFFFFFFFC) && !(dst & 0xFFFFFFFC)) {
        while (len >= 4) {
            *plDst++ = *plSrc++;
            len -= 4;
        }
    }

    char *pcDst = (char *)plDst;
    char const *pcDst = (char const *)plSrc;

    while (len--) {
         *pcDst++ = *pcSrc++;
    }

    return (dst);
}
Run Code Online (Sandbox Code Playgroud)

有人可以向我解释下面的行吗?

if (!(src & 0xFFFFFFFC) && !(dst & 0xFFFFFFFC))
Run Code Online (Sandbox Code Playgroud)

在这里,他们想检查srcdst地址是否与4 byte边界对齐。他们为什么要使用!,因为它false每次都会造成条件?

其次,上面的代码是否还有进一步优化的余地?

chq*_*lie 4

本文虽然讨论了一个有趣的主题,但未能提供正确的示例。发布的代码被称为GNU 的 newlib 源代码。GNU 项目和 newlib 团队都会惊讶地发现这个意想不到的收敛声明!newlib不是一个 GNU 项目,它的大部分源代码都没有获得 GPL 许可

这种优化的实现memcpy是不可移植的、次优的并且在许多方面都是不正确的。

该测试if (!(src & 0xFFFFFFFC) && !(dst & 0xFFFFFFFC))尝试检测srcdst地址是否在边界上对齐long。由于多种原因,它很麻烦且不可移植,而且正如您所注意到的那样,它是完全错误的:

  • void *到 的隐式转换int是丑陋的并且定义了实现。为了更好的可移植性,应该对指针进行强制转换(uintptr_t)
  • 0xFFFFFFFC假设类型long为 4 字节。这可能是不正确的,事实上,long在 64 位 Linux 和 Mac 系统上,类型的长度是 8 字节。
  • src & 0xFFFFFFFC不是对齐检查,也不太可能是0,4 字节边界对齐的预期测试是src & 3

此外,代码无法优化srcdst具有相同对齐但未在long边界上对齐的情况。

其他可能的改进包括展开循环、使用小值的开关、将从slen读取的字节组合起来,一旦其在边界上对齐就写入...srclongdstlong

这是一个改进的替代方案:

#include <stdint.h>

void *memcpy(void *dst, void const *src, size_t len) {
    unsigned char *pcDst = (unsigned char *)dst;
    unsigned char const *pcSrc = (unsigned char const *)src;

    if (len >= sizeof(long) * 2
    &&  ((uintptr_t)src & (sizeof(long) - 1)) == ((uintptr_t)dst & (sizeof(long) - 1))) {
        while (((uintptr_t)pcSrc & (sizeof(long) - 1)) != 0) {
            *pcDst++ = *pcSrc++;
            len--;
        }
        long *plDst = (long *)pcDst;
        long const *plSrc = (long const *)pcSrc;
        /* manually unroll the loop */
        while (len >= sizeof(long) * 4) {
            plDst[0] = plSrc[0];
            plDst[1] = plSrc[1];
            plDst[2] = plSrc[2];
            plDst[3] = plSrc[3];
            plSrc += 4;
            plDst += 4;
            len -= sizeof(long) * 4;
        }
        while (len >= sizeof(long)) {
            *plDst++ = *plSrc++;
            len -= sizeof(long);
        }
        pcDst = (unsigned char *)plDst;
        pcSrc = (unsigned char const *)plSrc;
    }
    while (len--) {
         *pcDst++ = *pcSrc++;
    }
    return dst;
}
Run Code Online (Sandbox Code Playgroud)

请注意,强制转换void *在 C 中是不必要的,但在 C++ 中是必需的。

在尝试优化代码以提高速度时,请记住以下几点:

  • 无需权衡正确性。即使在边界情况下也失败的快速代码是无用的。
  • 注意可移植性问题:依赖于类型大小和/或字节顺序的解决方案可能会产生可移植性问题。
  • 基准测试将告诉您什么有效,什么无效:您必须在各种测试用例上仔细计算各种替代方案的时间。
  • 没有完美的解决方案:一种架构上更快的代码在另一种架构上可能会更慢,包括未来的架构,这也是最有问题的。对不同架构进行基准测试。
  • 对抗复杂性:避免那些不会带来实质性改进的复杂解决方案,它们的正确性更难以证明和维护。
  • memcpy通常在汇编中进行优化或由现代编译器作为内置实现。