Memcpy实施中的优化

时间:2018-07-24 09:10:20

标签: c optimization

下面是我在搜索优化的memcpy实现时获得的代码。
这是link

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);
}

有人可以向我解释以下内容吗?

if (!(src & 0xFFFFFFFC) && !(dst & 0xFFFFFFFC))

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

第二,以上代码中是否还有进一步优化的空间?

1 个答案:

答案 0 :(得分:2)

本文虽然讨论了一个有趣的主题,但却没有提供正确的示例。发布的代码称为 GNU的newlib源代码。 GNU项目和newlib团队都将惊讶地得知这一意外的声明! newlib 不是GNU项目,并且其大部分源代码均未获得GPL许可。

memcpy优化实现是不可移植的,次优的,在许多方面都是不正确的。

测试if (!(src & 0xFFFFFFFC) && !(dst & 0xFFFFFFFC))试图检测srcdst地址是否在long边界上对齐。您会注意到,它繁琐且不便于携带,并且完全错误:

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

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

其他可能的改进包括展开循环,使用一个用于len的较小值的开关,将从src读取的字节组合到long s中,一旦写入dstlong边界上对齐...

这是一种改进的替代方法:

#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;
}

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

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

  • 没有权衡正确性。即使在边界情况下也无法通过的快速代码是没有用的。
  • 当心可移植性问题:取决于类型大小和/或字节顺序的解决方案可能会造成可移植性问题。
  • 基准测试将告诉您什么有效,哪些无效:您必须仔细考虑各种测试用例的各种替代方法的时间。
  • 没有完美的解决方案:一种架构上的更快代码在另一种架构上(包括未来的架构)可能会变慢。不同架构的基准测试。
  • 战斗的复杂性:避免复杂的解决方案并不能带来实质性的改进,其正确性更难以证明和维护。
  • memcpy通常在汇编中进行优化,或者由现代编译器内置实现。
相关问题