我看到了以下代码(PUZZLERSWORLD.COM网站上的问题3):
代码1:
for (i = 0; i < 1000; i++)
for (j = 0; j < 100; j++)
x = y;
代码2:
for (i = 0; i < 100; i++)
for (j = 0; j < 1000; j++)
x = y;
哪个代码执行得更快?
选项:
a) Code 1 and Code 2 are of same speed,
b) Code 1,
c) Code 2,
d) Can't Say
答案:
c)
所以,我有一个问题,为什么第二个代码比第一个代码更快?
答案 0 :(得分:13)
除非x
或y
或两者都声明为volatile
,否则这两个代码都可以缩减为:
x = y;
示例:
int f(int y)
{
int x;
for (int i = 0; i < 1000; i++)
for (int j = 0; j < 100; j++)
x = y;
return x;
}
的产率:
f(int):
mov eax, edi
ret
答案 1 :(得分:6)
问题似乎是假设,例如,增量将会完成在注册表中,不必在递增i
和j
之间切换并且过于频繁地重新初始化j
,这样会更有效率。这些假设可能坚持某些硬件,但它不太可能产生可衡量的差异。
正确的答案是:&#34; a),任何体面的编译器&#34;,因为一个体面的编译器创建的代码不会执行除了单个x = y;
使用此来源。 (因此两个片段都将编译为完全相同的可执行代码)
对于&#34;如何检查&#34;,您有两种可能性:
gcc
&#39; -S
选项)并进行比较(这是一个很简单的代码方法)。答案 2 :(得分:6)
回答d)
是正确的。一般来说,如果不参考所有细节,使用哪种编译器,使用哪种处理器架构等,你就不能多说性能。
我会停止使用该网站作为学习资源并找到另一个网站。
答案 3 :(得分:1)
循环有一些开销
代码1中内循环的开销称为1000次,而代码2则称为100次。
因此代码2更快或相同
即使编译器非常好地优化并删除了循环,代码2也永远不会慢一些
我选择选项e)
编译器根本不进行优化,而内部循环的开销适用,代码2更快。这将匹配选项D)或E)
编译器进行优化
b1)完全删除循环。
比两个代码都相同。选项A)或E)
b2)将两个循环合并为一个
比两个代码都相同。选项A)或E)
b3)组合重新排序循环(这将是原始代码中的一个愚蠢的子优化)
比两个代码都相同。选项A)或E)
因此,在结合所有案例后,只有E)是真的
Fabel“counterexample”无效,因为没有理由在代码1中应用优化但在代码2中没有应用优化
答案 4 :(得分:1)
因为初始化循环需要很少的开销(将计数器变量设置为零)。在代码1中,开销执行1 + 1000次,而在代码2中只执行1 + 100次。在两种情况下,使用真正的编译器至少会删除循环并将代码减少到x = y;
,因为它知道结果将是相同的。或者,如果它不能这样做(如果内部循环包含说f(i,j);
),它可能会决定在代码1中展开内循环但不在代码2中展开使代码1更快(可能至少是速度的两倍但是它当然取决于ISA和ABI。)
答案 5 :(得分:1)
根据变量的类型,编译器会以不同的方式优化代码。
答案:如果有任何优化,代码对foo执行完全相同,如果不稳定,因为寄存器加载次数更多
int x, y;
volatile int m,n;
void foo(void)
{
for(int i=0; i<1000; i++)
for(int j=0; j<100; j++)
x = y;
for(int i=0; i<100; i++)
for(int j=0; j<1000; j++)
x = y;
}
void foo1(void)
{
for(int i=0; i<1000; i++)
for(int j=0; j<100; j++)
m = n;
for(int i=0; i<100; i++)
for(int j=0; j<1000; j++)
m = n;
}
和生成的代码ARM :( x86 verison here:https://godbolt.org/g/tSRKxy)。
foo():
ldr r3, .L2
ldr r2, [r3, #4]
str r2, [r3]
bx lr
.L2:
.word .LANCHOR0
foo1():
mov r0, #1000
ldr r3, .L14
.L6:
mov r2, #100
.L5:
ldr r1, [r3, #8]
subs r2, r2, #1
str r1, [r3, #12]
bne .L5
subs r0, r0, #1
bne .L6
mov r0, #100
.L8:
mov r2, #1000
.L7:
ldr r1, [r3, #8]
subs r2, r2, #1
str r1, [r3, #12]
bne .L7
subs r0, r0, #1
bne .L8
bx lr
.L14:
.word .LANCHOR0
x:
y:
n:
m:
答案 6 :(得分:1)
问题是缺少任何记录答案的重要信息。
例如,如果我们有这些程序:
int main(void) {
int i, j, x, y;
for (i = 0; i < 1000; i++)
for (j = 0; j < 100; j++)
x = y;
}
和
int main(void) {
int i, j, x, y;
for (i = 0; i < 100; i++)
for (j = 0; j < 1000; j++)
x = y;
}
两者都有未定义的行为,因为它们读取未初始化变量y
的值。
如果我们有这个:
int main(void) {
int i;
unsigned char j;
int x, y = 0;
for (i = 0; i < 1000; i++)
for (j = 0; j < 100; j++)
x = y;
}
和
int main(void) {
int i;
unsigned char j;
int x, y = 0;
for (i = 0; i < 1000; i++)
for (j = 0; j < 100; j++)
x = y;
}
第二个代码永远运行,第一个代码立即完成。
通过合适的编译器和正确的定义,代码几乎完全优化,因此回答 a):
int main(void) {
int i, j, x, y = 0;
for (i = 0; i < 100; i++)
for (j = 0; j < 1000; j++)
x = y;
}
使用Godbolt's compiler explorer编译,产生:
main:
xorl %eax, %eax
ret
网站希望您选择 c),因为嵌套循环的初始开销在第二个代码片段中比第一个重复10倍。虽然在这个分析中有一些逻辑,但它可能不是主导因素,并且如上所述,其他考虑因素也必须进行研究,并且在任何情况下,900个额外指令可以忽略不计,而其余代码则为数十万个,使其难以衡量。
在绩效分析中,谦逊是一种重要的美德。使用分析工具并仔细编写完整的基准测试代码,以确定优化是否有用。编写复杂代码以尝试优化并获取产生错误结果的代码时,这是一个常见的错误。
请注意,链接页面中存在破碎的问题。例如Q2:
#include
fun(int i)
{
int j=0;
while (i & i-1) {
j++;
}
printf("%d",j);
}
main(){
f(100);
}
选项:a. 100 b. 99 c. 2 d. 3
代码无法编译,如果更正了,它有一个无限循环,因此没有一个答案是正确的。好奇的是,计算i
中位数的正确函数是:
void fun(unsigned i) {
int j = 0;
while (i) {
i = i & (i - 1);
j++;
}
printf("%d\n", j);
}
在10个问题中,7个答案不正确,其余3个(Q1,Q4和Q5)是语法错误的技巧问题。确实是质量很差的Q&amp; A网站。