我目前正在做一个大学项目,该项目在很大程度上取决于我的解决方案的速度和效率。我对代码所做的微小改动产生了巨大的影响,因为我写的特定功能被称为成千上万次。
我现在已经编写了我项目的主要功能,目前我正在优化我可能做的所有事情。我正在质疑的代码的一个特定部分是这样的:
array[i] *= -1;
我正在考虑优化:
array[i] = 0 - array[i];
更改此代码会不会影响速度?减法运算比乘法运算更快吗?或者这类问题已成为过去?
答案 0 :(得分:18)
忽略你应该使用它的事实:
array[i] = -array[i];
因为IMO直接表明了意图,所以它更加清晰,让我们检查编译器对此程序的作用(x86-64上的GCC 4.7.2):
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t t = time(NULL);
t *= -1;
return 0;
}
gcc -S mult.c -o 1.s
为此:
#include <stdio.h>
#include <time.h>
int main(void)
{
time_t t = time(NULL);
t = 0 - t;
return 0;
}
gcc -S sub.c -o 2.s
现在比较两个汇编输出:
diff 1.s 2.s
什么都没打印出来。编译器为两个版本生成了相同的确切代码。所以答案是:你使用什么并不重要。编译器会选择最快的。这是一个非常容易的优化(如果你甚至可以称之为优化),所以我们可以假设几乎每个编译器都会选择以最快的方式为给定的CPU架构做这件事。
作为参考,生成的代码是:
int main() { time_t t = time(NULL); mov edi,0x0 call 12 mov QWORD PTR [rbp-0x8],rax t *= -1; neg QWORD PTR [rbp-0x8] t = 0 - t; neg QWORD PTR [rbp-0x8] return 0; mov eax,0x0 }
在这两种情况下,它都使用NEG来否定该值。 t *= -1
和t = 0 - t
都生成:
neg QWORD PTR [rbp-0x8]
答案 1 :(得分:13)
只有一种明智的优化方法,那就是测量应用程序的性能。一个好的剖析器可以告诉你很多,但只是计划程序的执行时间和各种修改也可以提供很大的帮助。我会首先使用探查器,但要找出瓶颈所在。
至于你的具体问题,正如其他人所指出的那样,这将高度依赖于架构。
答案 2 :(得分:5)
编译器非常聪明,可以将其转换为高效的操作。例如
C源
void f()
{
int a = 7, b = 7;
a *= -1;
b = -b;
}
使用gcc -S a.c
.file "a.c"
.text
.globl f
.type f, @function
f:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $7, -8(%rbp) ; assign 7
movl $7, -4(%rbp) ; assign 7
negl -8(%rbp) ; negate variable
negl -4(%rbp) ; negate variable
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size f, .-f
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",@progbits
这是在使用Ubuntu 12.04和gcc 4.6.3的PC上。您的架构可能会有所不同。
答案 3 :(得分:3)
几乎每台设备的乘法速度都会变慢。这是一个更复杂的操作。
但是你的编译器可能足够聪明,可以自己进行转换。 并且在现代CPU上,操作可以以这样的方式重叠:指令的额外时间不会导致执行时间增加,因为它与其他工作重叠。 而且,除非你做了数百万次,否则如果没有大量的努力就会无法衡量,这种差异很小。
通常首先编写清除代码,如果以后需要优化代码。 如果你想要一个变量的负值写“-value”而不是“-1 * value”,因为它更准确地反映了你的意图,而不仅仅是一种计算方法。
答案 4 :(得分:2)
这是gcc 4.6.1对-O:
的作用double a1(double b) { return -b; } // xors sign bit with constant, 2 instr
// movsd .LC0(%rip), %xmm1 (instr 1)
// xorpd %xmm1, %xmm0 (instr 2)
// ret (not counted)
double a2(double b) { return -1.0*b; } // xors sign bit with constant, 2 instr
// same code as above
double a3(double b) { return 0.0-b; } // substract b from 0, 3 instructions
// xorpd %xmm1, %xmm1
// subsd %xmm0, %xmm1
// movapd %xmm1, %xmm0 (+ret)
int a4(int a){return -a;} // neg rax (+ret) 1 instruction
int a5(int a){return a*(-1);} // neg rax
int a6(int a){return 0-a;} // neg rax
double a7(double b) { return 0-b;} // same as a3() -- 3 instructions
因此,建议的优化使得此编译器更糟糕(取决于数组类型)。
然后关于乘法的问题比加法慢。根据经验,如果乘法与加法一样快,我们讨论的是DSP架构或DSP扩展:Texas C64,Arm NEON,Arm VFP,MMX,SSE。对于许多浮点扩展,从Pentium开始也是如此,其中FADD和FMUL都具有3个周期的延迟和每个周期1个指令的吞吐量。 ARM整数核也在1个周期内执行乘法运算。
答案 5 :(得分:1)
好的,试着清理我的烂摊子,把我的愚蠢变成有用的知识,不仅是为了我自己,也是为了其他人。
这种优化是由编译器自动完成的,在这种情况下,两种方法都被编译为x86上的一条ASM指令。(参见上面的帖子)不要让编译器的工作比它必须是,只要做逻辑所暗示的。
几个答案显示,这两种方式都编译为完全相同的指令。
<强> TL; DR 强>
为了解决我在这个问题上所犯的错误,我决定投入一些努力来为自己清除这一点 - 对于那些遭受精神错乱的人,就像我在回答这个问题时所做的那样奇怪的错误答案...... / p>
否定数字取决于体系结构以及数据的表示方式。
不知怎的,我假设使用了这个实现 - 事实并非如此。这表示数字为一个符号位,所有其余值表示该值。因此,它可以表示从-2 n-1 -1到2 n-1 -1的数字,并且也具有负零值。在这种表示中,翻转符号位就足够了:
input ^ -0; // as the negative zero has all bits but the MSB as zero
A one's complement integer representation表示负数作为正表示的按位否定。然而,这并没有真正使用,从8080开始,使用了两个赞美。这种表示的一个奇怪后果是负零,这可能会带来很多麻烦。此外,所表示的数字范围从-2 n-1 -1到2 n-1 -1,其中n是存储数字的位数。
在这种情况下,否定数字的最快“手动”方式是翻转代表符号的所有位:
input ^ 0xFFFFFFFF; //assuming 32 bits architecture
或
input ^ -0; //as negative zero is a "full one" binary value
更广泛(总是?)使用的表示是two's complement system。它表示从-2 n-1 到2 n-1 -1的数字,并且只有一个零值。它表示正范围作为它们的普通二进制表示。然而,添加1到2 n-1 -1(由除了MSB之外的所有位中的1表示)将导致-2 n-1 (表示为MSB为1,其他所有位为零。
手动否定一个二进制补码需要否定所有位并加1:
(input ^ -1) + 1 //as -1 is represented by all bits as 1
然而因为负值的范围比正值的范围更广,所以最负数在此表示中没有正对应,这在交易时必须计算在内。这些数字!反转最负值将导致其本身,就像它发生零(为简单起见,8位)
most negative number: -128, represented as 10000000
inverting all bits: 01111111
adding one: 10000000 -> -128 again
但请大家*记住:过早优化是万恶之源! (对于任何资源丰富的架构,优化器都是过去的事情)
*:OP已经通过了这个,所以这适用于所有其他人,比如我。
(注意自我:过早地(过早地)愚蠢是所有(正当的)downvotes的根源。)