我一直想知道,是否在所有情况下都等同于这样写:
char* op = buf;
int l = buflen;
while( l > 0 )
{
*op = bufval;
op++;
l--;
}
以这种方式:
int l;
for( l = 0; l < buflen; l++ )
{
buf[ l ] = bufval;
}
从众多现代编译器和计算平台的性能角度来看?后者的代码当然更优雅,但这不是重点。我看到检查“ l大于0”就像是汇编程序中的简单JNZ,而“ l小于bufval”则需要比较op。 “ buf [l]”可能不需要相对于“ op ++”的附加指令,但是我不知道这在实践中如何影响性能。在某些情况下,例如“ op”需要增加3时,第一个变体是可取的,这比我想写“ l * 3”要好得多。
答案 0 :(得分:2)
这是两个代码段的生成程序集
f2: ;; pointer based approach
test edx, edx
jle .L5
sub edx, 1
movsx esi, sil
movsx rdx, edx
add rdx, 1
jmp memset
.L5:
ret
f3: ;; loop based approach
test edx, edx
jle .L8
sub edx, 1
movsx esi, sil
add rdx, 1
jmp memset
.L8:
ret
我知道较短的汇编并不意味着更快的代码,但是编译器确实为基于指针的版本生成了一些额外的指令。当我使用clang尝试相同指令时,指令数量的差异甚至更大。如果有的话,基于指针的版本会稍微慢一些,而不是更快。
请注意,这两者都在调用memset
,在此之前的代码仅是检查和设置用于memset
调用的寄存器。您可以继续进行memset
通话。
memset(buf, bufval, buflen)
这将生成以下程序集
f1: ;; memset based approach
movsx rdx, edx
jmp memset
回到最初的问题,哪个版本更快。不能过分强调现代编译器很聪明。像这样的微优化很少(如果有的话)提供性能优势。编写惯用代码(使编译器更容易理解其意图)将始终为您带来更好的性能。
如果您要使用程序集输出:https://godbolt.org/g/NxHS5F
,这里是指向Godbolt的链接。答案 1 :(得分:1)
您看到的差异仅是因为您的函数是使用非常不友好且糟糕的方式以非常好的编译器优化器编写的。
如果编写得更好,两者都应编译相同:
void foo(char *buf, int bufval, size_t buflen)
{
while(buflen--)
{
*buf++ = bufval;
}
}
void foo1(char *buf, int bufval, size_t buflen)
{
size_t l;
for( l = 0; l < buflen; l++ )
{
buf[ l ] = bufval;
}
}
foo:
test rdx, rdx
je .L11
movsx esi, sil
jmp memset
.L11:
ret
foo1:
test rdx, rdx
je .L14
movsx esi, sil
jmp memset
.L14:
ret
在程序员方面,优化的质量为80%。如果您像在问题中那样编写程序,则总是会得到较差的机器代码。
与-fno-builtin
void foo(char *buf, int bufval, size_t buflen)
{
while(buflen--)
{
*buf++ = bufval;
}
}
void foo1(char *buf, int bufval, size_t buflen)
{
size_t l;
for( l = 0; l < buflen; l++ )
{
buf[ l ] = bufval;
}
}
foo:
lea rax, [rdi+rdx]
test rdx, rdx
je .L9
.L11:
add rdi, 1
mov BYTE PTR [rdi-1], sil
cmp rdi, rax
jne .L11
.L9:
ret
foo1:
lea rax, [rdi+rdx]
test rdx, rdx
je .L15
.L17:
mov BYTE PTR [rdi], sil
add rdi, 1
cmp rdi, rax
jne .L17
.L15:
ret