if语句和mod(SIZE)之间的效率差异

时间:2019-06-13 16:35:27

标签: c performance if-statement processing-efficiency

研究我发现使用(i+1)mod(SIZE)在元素数组中执行循环。 所以我想知道这种方法是否比if语句更有效...


例如:

#define SIZE 15

int main(int argc, char *argv[]) {
    int items[SIZE];

    for(int i = 0; items[0] < 5; i = (i + 1) % SIZE) items[i] += 1;

    return 0;
}

比(?)更有效:

#define SIZE 15

int main(int argc, char *argv[]) {
    int items[SIZE];

    for(int i = 0; items[0] < 5; i++) {
        if(i == SIZE) i = 0;
        items[i] += 1;
    }

    return 0;
}

感谢您的回答和时间。

2 个答案:

答案 0 :(得分:3)

您可以在线检查程序集(即here)。结果取决于体系结构和优化,但是没有优化,对于带有GCC的x64,您将获得此代码(作为简单示例)。

示例1:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-68], edi
        mov     QWORD PTR [rbp-80], rsi
        mov     DWORD PTR [rbp-4], 0
.L3:
        mov     eax, DWORD PTR [rbp-64]
        cmp     eax, 4
        jg      .L2
        mov     eax, DWORD PTR [rbp-4]
        cdqe
        mov     eax, DWORD PTR [rbp-64+rax*4]
        lea     edx, [rax+1]
        mov     eax, DWORD PTR [rbp-4]
        cdqe
        mov     DWORD PTR [rbp-64+rax*4], edx
        mov     eax, DWORD PTR [rbp-4]
        add     eax, 1
        movsx   rdx, eax
        imul    rdx, rdx, -2004318071
        shr     rdx, 32
        add     edx, eax
        mov     ecx, edx
        sar     ecx, 3
        cdq
        sub     ecx, edx
        mov     edx, ecx
        mov     DWORD PTR [rbp-4], edx
        mov     ecx, DWORD PTR [rbp-4]
        mov     edx, ecx
        sal     edx, 4
        sub     edx, ecx
        sub     eax, edx
        mov     DWORD PTR [rbp-4], eax
        jmp     .L3
.L2:
        mov     eax, 0
        pop     rbp
        ret

示例2:

main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-68], edi
        mov     QWORD PTR [rbp-80], rsi
        mov     DWORD PTR [rbp-4], 0
.L4:
        mov     eax, DWORD PTR [rbp-64]
        cmp     eax, 4
        jg      .L2
        cmp     DWORD PTR [rbp-4], 15
        jne     .L3
        mov     DWORD PTR [rbp-4], 0
.L3:
        mov     eax, DWORD PTR [rbp-4]
        cdqe
        mov     eax, DWORD PTR [rbp-64+rax*4]
        lea     edx, [rax+1]
        mov     eax, DWORD PTR [rbp-4]
        cdqe
        mov     DWORD PTR [rbp-64+rax*4], edx
        add     DWORD PTR [rbp-4], 1
        jmp     .L4
.L2:
        mov     eax, 0
        pop     rbp
        ret

您看到,对于使用x86的特定情况,不带模的解决方案要短得多。

答案 1 :(得分:1)

尽管您仅询问modbranch,但根据mod和分支的实际实现,可能会有更多类似的五种情况:

基于模量

二次幂

如果SIZE的值是编译器已知的并且是2的幂,则mod将编译为单个and like this,并且非常有效性能和代码大小。 and仍然是循环增量依赖关系链的一部分,除非迭代器足够聪明地展开and并将其SIZE排除在外,否则speed limit会在每次迭代的2个周期的性能上使用的链条(不是gcc和clang)。

已知,不是二的幂

反之,如果SIZE的值是已知的,但不是2的幂,那么您很可能会获得基于乘法的固定模量值like this的实现。这通常需要4-6条指令,它们最终成为依赖链的一部分。因此,这将使您的性能每5-8个周期受限于1次迭代,具体取决于依赖链的延迟。

未知

在您的示例中,SIZE是一个已知常量,但是在更一般的情况下,在编译时未知该常量,您将在支持该常量的平台上获得除法指令。 like this

这对于代码大小来说是有好处的,因为它是一条指令,但是对于性能而言可能是灾难性的,因为现在您将慢速除法指令作为循环的已依赖项的一部分。根据您的硬件和-O2变量的类型,您每次迭代需要20-100个周期。

基于分支

您在代码中放入了一个分支,但是跳转编译器决定将其作为条件跳转或条件移动来实现。在i == SIZEgcc decides on a jump and clang on a conditional move

有条件的跳跃

这是您的代码的直接解释:使用条件分支来实现SIZE条件。

它具有使条件成为控制依赖项而不是数据依赖项的优点,因此,当不采用分支时,循环将以全速运行。

但是,如果分支经常错误地预测,则性能可能会受到严重影响。这在很大程度上取决于SIZE的值和您的硬件。现代的Intel应该能够预测多达20多个迭代的嵌套循环,但是除此之外,每次退出内部循环时,它都会发生错误的预测。当然,SIZE很大,那么单个错误预测无论如何都不会有多大关系,因此最坏的情况是i足够大,足以导致错误预测。

有条件的移动

clang使用条件移动来更新case_when。这是一个合理的选择,但是它确实意味着要携带3-4个周期的数据流。


1 像您的示例一样实际上一个常数,或者由于内联和恒定传播而实际上是一个常数。