哪个代码在C中执行得更快?

时间:2017-08-17 09:01:19

标签: c performance loops

我看到了以下代码(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)

所以,我有一个问题,为什么第二个代码比第一个代码更快?

7 个答案:

答案 0 :(得分:13)

除非xy或两者都声明为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)

问题似乎是假设,例如,增量将会完成在注册表中,不必在递增ij之间切换并且过于频繁地重新初始化j,这样会更有效率。这些假设可能坚持某些硬件,但它不太可能产生可衡量的差异。

正确的答案是:&#34; a),任何体面的编译器&#34;,因为一个体面的编译器创建的代码不会执行除了单个x = y;使用此来源。 (因此两个片段都将编译为完全相同的可执行代码)

对于&#34;如何检查&#34;,您有两种可能性:

  • 编译为汇编代码(例如,使用gcc&#39; -S选项)并进行比较(这是一个很简单的代码方法)。
  • 措施。经常运行每个代码 (如一百万次)并花时间。

答案 2 :(得分:6)

回答d)是正确的。一般来说,如果不参考所有细节,使用哪种编译器,使用哪种处理器架构等,你就不能多说性能。

我会停止使用该网站作为学习资源并找到另一个网站。

答案 3 :(得分:1)

循环有一些开销

代码1中内循环的开销称为1000次,而代码2则称为100次。

因此代码2更快或相同

即使编译器非常好地优化并删除了循环,代码2也永远不会慢一些

我选择选项e)

PROVE

的假设

  • 使用两个代码的编译器
  • 对两个代码使用优化时
  • 仅使用理性优化,不使用随机或奇怪的
  • 任何代码都需要时间执行

案例a)

编译器根本不进行优化,而内部循环的开销适用,代码2更快。这将匹配选项D)或E)

案例b)

编译器进行优化

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网站。