Dev*_*son 30 c optimization performance unsigned signed
在C中,为什么signed int
速度比unsigned int
?是的,我知道这个网站已被多次询问和回答(链接如下).但是,大多数人说没有区别.我编写了代码并意外地发现了显着的性能差异.
为什么我的代码的"未签名"版本比"签名"版本慢(即使在测试相同的数字时)?(我有一个x86-64英特尔处理器).
类似的链接
编译命令: gcc -Wall -Wextra -pedantic -O3 -Wl,-O3 -g0 -ggdb0 -s -fwhole-program -funroll-loops -pthread -pipe -ffunction-sections -fdata-sections -std=c11 -o ./test ./test.c && strip --strip-all --strip-unneeded --remove-section=.note --remove-section=.comment ./test
signed int
版注意:如果我明确声明signed int
所有数字,则没有区别.
int isprime(int num) {
// Test if a signed int is prime
int i;
if (num % 2 == 0 || num % 3 == 0)
return 0;
else if (num % 5 == 0 || num % 7 == 0)
return 0;
else {
for (i = 11; i < num; i += 2) {
if (num % i == 0) {
if (i != num)
return 0;
else
return 1;
}
}
}
return 1;
}
Run Code Online (Sandbox Code Playgroud)
unsigned int
版int isunsignedprime(unsigned int num) {
// Test if an unsigned int is prime
unsigned int i;
if (num % (unsigned int)2 == (unsigned int)0 || num % (unsigned int)3 == (unsigned int)0)
return 0;
else if (num % (unsigned int)5 == (unsigned int)0 || num % (unsigned int)7 == (unsigned int)0)
return 0;
else {
for (i = (unsigned int)11; i < num; i += (unsigned int)2) {
if (num % i == (unsigned int)0) {
if (i != num)
return 0;
else
return 1;
}
}
}
return 1;
}
Run Code Online (Sandbox Code Playgroud)
使用以下代码在文件中测试:
int main(void) {
printf("%d\n", isprime(294967291));
printf("%d\n", isprime(294367293));
printf("%d\n", isprime(294967293));
printf("%d\n", isprime(294967241)); // slow
printf("%d\n", isprime(294967251));
printf("%d\n", isprime(294965291));
printf("%d\n", isprime(294966291));
printf("%d\n", isprime(294963293));
printf("%d\n", isprime(294927293));
printf("%d\n", isprime(294961293));
printf("%d\n", isprime(294917293));
printf("%d\n", isprime(294167293));
printf("%d\n", isprime(294267293));
printf("%d\n", isprime(294367293)); // slow
printf("%d\n", isprime(294467293));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
结果(time ./test
):
Signed - real 0m0.949s
Unsigned - real 0m1.174s
Run Code Online (Sandbox Code Playgroud)
chq*_*lie 16
您的问题确实很有趣,因为无符号版本始终产生的代码速度慢了10%到20%.然而,代码中存在多个问题:
0
的2
,3
,5
和7
,这是不正确.if (i != num) return 0; else return 1;
循环体仅用于运行,因此测试完全没用i < num
.这样的测试对于小型主要测试是有用的,但是特殊的套管它们并不真正有用.clock()
函数来计算CPU密集型功能,而无需任何干预I/O.num / 2
时而不是sqrt(num)
.让我们简化代码并运行一些精确的基准测试:
#include <stdio.h>
#include <time.h>
int isprime_slow(int num) {
if (num % 2 == 0)
return num == 2;
for (int i = 3; i < num; i += 2) {
if (num % i == 0)
return 0;
}
return 1;
}
int unsigned_isprime_slow(unsigned int num) {
if (num % 2 == 0)
return num == 2;
for (unsigned int i = 3; i < num; i += 2) {
if (num % i == 0)
return 0;
}
return 1;
}
int isprime_fast(int num) {
if (num % 2 == 0)
return num == 2;
for (int i = 3; i * i <= num; i += 2) {
if (num % i == 0)
return 0;
}
return 1;
}
int unsigned_isprime_fast(unsigned int num) {
if (num % 2 == 0)
return num == 2;
for (unsigned int i = 3; i * i <= num; i += 2) {
if (num % i == 0)
return 0;
}
return 1;
}
int main(void) {
int a[] = {
294967291, 0, 294367293, 0, 294967293, 0, 294967241, 1, 294967251, 0,
294965291, 0, 294966291, 0, 294963293, 0, 294927293, 1, 294961293, 0,
294917293, 0, 294167293, 0, 294267293, 0, 294367293, 0, 294467293, 0,
};
struct testcase { int (*fun)(); const char *name; int t; } test[] = {
{ isprime_slow, "isprime_slow", 0 },
{ unsigned_isprime_slow, "unsigned_isprime_slow", 0 },
{ isprime_fast, "isprime_fast", 0 },
{ unsigned_isprime_fast, "unsigned_isprime_fast", 0 },
};
for (int n = 0; n < 4; n++) {
clock_t t = clock();
for (int i = 0; i < 30; i += 2) {
if (test[n].fun(a[i]) != a[i + 1]) {
printf("%s(%d) != %d\n", test[n].name, a[i], a[i + 1]);
}
}
test[n].t = clock() - t;
}
for (int n = 0; n < 4; n++) {
printf("%21s: %4d.%03dms\n", test[n].name, test[n].t / 1000), test[n].t % 1000);
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
clang -O2
在OS/X上编译的代码会产生以下输出:
isprime_slow: 788.004ms
unsigned_isprime_slow: 965.381ms
isprime_fast: 0.065ms
unsigned_isprime_fast: 0.089ms
Run Code Online (Sandbox Code Playgroud)
这些时间与OP在不同系统上观察到的行为一致,但显示了更高效的迭代测试带来的显着改进:快10000倍!
关于问题为什么函数在无符号时变慢?,让我们看一下生成的代码(gcc 7.2 -O2):
isprime_slow(int):
...
.L5:
movl %edi, %eax
cltd
idivl %ecx
testl %edx, %edx
je .L1
.L4:
addl $2, %ecx
cmpl %esi, %ecx
jne .L5
.L6:
movl $1, %edx
.L1:
movl %edx, %eax
ret
unsigned_isprime_slow(unsigned int):
...
.L19:
xorl %edx, %edx
movl %edi, %eax
divl %ecx
testl %edx, %edx
je .L22
.L18:
addl $2, %ecx
cmpl %esi, %ecx
jne .L19
.L20:
movl $1, %eax
ret
...
.L22:
xorl %eax, %eax
ret
Run Code Online (Sandbox Code Playgroud)
内部循环非常相似,指令数量相同,类似指令.然而,这里有一些可能的解释:
cltd
将eax
寄存器的符号扩展到edx
寄存器中,这可能导致指令延迟,因为eax
由前一条指令修改movl %edi, %eax
.然而,这会使签名版本比未签名版本慢,而不是更快.idivl
指令可能比指令占用的周期少divl
.实际上,带符号的除法运算精度比无符号除法的精度低一点,但这种微小变化的差异似乎很大.idivl
因为签名分区比无符号分区更常见(根据英特尔多年的编码统计数据来衡量).这个令人惊讶的结果应该教给我们一些教训:
由于带符号整数溢出是不确定的,因此编译器可以对涉及带符号整数的代码进行大量假设和优化。无符号整数溢出被定义为可环绕,因此编译器将无法进行尽可能多的优化。另请参见http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html#signed_overflow和http://www.airs.com/blog/archives/120。
根据AMD/Intel 的指令规范,我们有(对于 K7):
\n\nInstruction Ops Latency Throughput\nDIV r32/m32 32 24 23\nIDIV r32 81 41 41\nIDIV m32 89 41 41 \n
Run Code Online (Sandbox Code Playgroud)\n\nIDIVL
对于 i7,延迟和吞吐量相同DIVL
,\xc2\xb5ops 存在细微差别。
这可以解释这种差异,因为 -O3 汇编代码仅在我的机器上的符号不同(DIVL 与 IDIVL)。
\n 归档时间: |
|
查看次数: |
4672 次 |
最近记录: |