我查看了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 副本,而在clang 和gcc 中,int i 或size_t i 都没有。据推测,他们很难考虑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 和分支预测器缓存(用作实际代码的一小部分)。大部分复制在 16B 块中完成,64 位、32 位和更小的块位于启动/清理部分。
当然,使用strcpy 也可以避免忘记在目标中存储尾随'\0' 字符等错误。如果您的输入字符串可能很大,使用int 作为循环计数器(而不是size_t)也是一个错误。使用strncpy 通常会更好,因为您通常知道 dest 缓冲区的大小,但不知道 src 的大小。
memcpy 可能比strcpy 更高效,因为rep movs 在 Intel CPU 上进行了高度优化,尤其是。 IVB 及更高版本。但是,首先扫描字符串以找到正确的长度总是比差价要高。如果您已经知道数据的长度,请使用memcpy。