c++ - 循环复制是否比 memcpy() 效率低?

标签 c++ performance loops memcpy strlen

我开始学IT,现在正在和一个 friend 讨论这段代码是不是低效。

// const char *pName
// char *m_pName = nullptr;

for (int i = 0; i < strlen(pName); i++)
    m_pName[i] = pName[i];

他声称例如 memcopy 会像上面的 for 循环一样做同样的事情。我想知道这是不是真的,我不相信。

如果有更有效的方法或者这种方法效率低下,请告诉我原因!

提前致谢!

最佳答案

我看了一下actual g++ -O3 output for your code ,看看它有多糟糕。

char* 可以为任何东西起别名,所以即使是 __restrict__ GNU C++ 扩展也无法帮助编译器将 strlen 提升到循环之外.

我以为它会被吊起,并预计这里的主要低效率只是一次字节复制循环。但不,它真的和其他答案所暗示的一样糟糕。 m_pName 甚至每次都必须重新加载,因为别名规则允许 m_pName[i] 别名 this->m_pName编译器不能假定存储到 m_pName[i] 不会更改类成员变量、src 字符串或任何其他内容。

#include <string.h>
class foo {
   char *__restrict__ m_pName = nullptr;
   void set_name(const char *__restrict__ pName);
   void alloc_name(size_t sz) { m_pName = new char[sz]; }
};

// g++ will only emit a non-inline copy of the function if there's a non-inline definition.
void foo::set_name(const char * __restrict__ pName)
{
    // char* can alias anything, including &m_pName, so the loop has to reload the pointer every time
    //char *__restrict__ dst = m_pName;  // a local avoids the reload of m_pName, but still can't hoist strlen
    #define dst m_pName
    for (unsigned int i = 0; i < strlen(pName); i++)
        dst[i] = pName[i];
}

编译成这个 asm(g++ -O3 for x86-64,SysV ABI):

...
.L7:
        movzx   edx, BYTE PTR [rbp+0+rbx]      ; byte load from src.  clang uses mov al, byte ..., instead of movzx.  The difference is debatable.
        mov     rax, QWORD PTR [r12]           ; reload this->m_pName    
        mov     BYTE PTR [rax+rbx], dl         ; byte store
        add     rbx, 1
.L3:                                 ; first iteration entry point
        mov     rdi, rbp                       ; function arg for strlen
        call    strlen
        cmp     rbx, rax
        jb      .L7               ; compare-and-branch (unsigned)

使用 unsigned int 循环计数器会引入一个额外的 mov ebx, ebp 循环计数器拷贝,这是 int i< 所没有的size_t i,在 clang 和 gcc 中。据推测,他们很难解释 unsigned i 可能产生无限循环这一事实。

很明显这很可怕:

  • 对复制的每个字节调用 strlen
  • 一次复制一个字节
  • 每次循环都重新加载 m_pName(可以通过将其加载到本地来避免)。

使用 strcpy 可以避免所有这些问题,因为 strlen 可以假设它的 src 和 dst 不重叠。使用 strlen + memcpy 除非你想自己了解 strlen 。如果 strcpy 的最有效实现是 strlen + memcpy,库函数将在内部执行此操作。否则,它会做一些更有效率的事情,比如 glibc's hand-written SSE2 strcpy for x86-64 . (有一个 SSSE3 version ,但它在 Intel SnB 上实际上更慢,而 glibc 足够聪明,不会使用它。)即使是 SSE2 版本也可能展开得比它应该的多(在微基准测试上很好,但会污染指令缓存, uop-cache 和 branch-predictor 缓存(当用作实际代码的一小部分时)。大部分复制是在 16B block 中完成的,启动/清理部分有 64 位、32 位和更小的 block 。

当然,使用 strcpy 也可以避免错误,例如忘记在目标中存储尾随 '\0' 字符。如果您的输入字符串可能很大,使用 int 作为循环计数器(而不是 size_t)也是一个错误。使用 strncpy 通常更好,因为您通常知道目标缓冲区的大小,但不知道源缓冲区的大小。

memcpystrcpy 更高效,因为 rep movs 在 Intel CPU 上进行了高度优化,尤其是。 IvB 及更高版本。然而,首先扫描字符串以找到正确的长度总是比差值花费更多。当您已经知道数据的长度时,请使用 memcpy

关于c++ - 循环复制是否比 memcpy() 效率低?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35385157/

相关文章:

Java 8 : How to list files that don't match pattern using regex

php - Twig - 打印没有空值的数组

c++ - 在没有 VBO 的情况下在 opengl 3.1+ 中呈现

c++ - 在以下情况下,您将如何声明参数类型

c++ - 使用指针成员对结构进行类型转换

javascript - 返回页面加载时间作为警报不起作用

c++ - 在 C++ 中内联所有方法,没有 Cpp 文件?

c++ - 取消引用 end() 是否安全?

performance - MongoDB 在聚合查询上的表现

json - AngularJS - 嵌套 forEach 来迭代 Json