Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/loops/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
展开循环(C)_C_Loops_Performance Testing_Unroll - Fatal编程技术网

展开循环(C)

展开循环(C),c,loops,performance-testing,unroll,C,Loops,Performance Testing,Unroll,我理解展开循环的概念,但是,有人能给我解释一下如何展开一个简单的循环吗 如果你能给我看一个循环,然后是一个循环的展开版本,并解释正在发生的事情,那就太好了 #define N 44 int main() { int A[N], B[N]; int i; // fill A with stuff ... for(i = 0; i < N; i++) { B[i] = A[i] * (100 % i); } // do

我理解展开循环的概念,但是,有人能给我解释一下如何展开一个简单的循环吗

如果你能给我看一个循环,然后是一个循环的展开版本,并解释正在发生的事情,那就太好了

#define N 44

int main() {
    int A[N], B[N];
    int i;

    // fill A with stuff ...

    for(i = 0; i < N; i++) {
        B[i] = A[i] * (100 % i);
    }

    // do stuff with B ...
}
展开:

#define N 44

int main() {
    int A[N], B[N];
    int i;

    // fill A with stuff ...

    for(i = 0; i < N; i += 4) {
        B[i] = A[i] * (100 % i);
        B[i+1] = A[i+1] * (100 % i+1);
        B[i+2] = A[i+2] * (100 % i+2);
        B[i+3] = A[i+3] * (100 % i+3);
    }

    // do stuff with B ...
}
展开可能会以更大的程序大小为代价来提高性能。性能的提高可能是由于分支惩罚、缓存未命中和执行指令的减少。有些缺点是显而易见的,比如代码量的增加和可读性的降低,有些则不那么明显。

#define N 44

int main() {
    int A[N], B[N];
    int i;

    // fill A with stuff ...

    for(i = 0; i < N; i++) {
        B[i] = A[i] * (100 % i);
    }

    // do stuff with B ...
}
展开:

#define N 44

int main() {
    int A[N], B[N];
    int i;

    // fill A with stuff ...

    for(i = 0; i < N; i += 4) {
        B[i] = A[i] * (100 % i);
        B[i+1] = A[i+1] * (100 % i+1);
        B[i+2] = A[i+2] * (100 % i+2);
        B[i+3] = A[i+3] * (100 % i+3);
    }

    // do stuff with B ...
}

展开可能会以更大的程序大小为代价来提高性能。性能的提高可能是由于分支惩罚、缓存未命中和执行指令的减少。有些缺点是显而易见的,比如代码量增加和可读性降低,有些则不那么明显。

展开循环的过程利用了计算机科学中的一个基本概念:时空权衡,增加使用的空间通常会导致算法的时间缩短

假设我们有一个简单的循环

const int n = 1000;

for (int i = 0; i < n; ++i) {
    foo();
}
因此,时空折衷是对执行的~4*1000=~4000条指令进行5行汇编

那么,让我们试着稍微展开这个循环

for (int i = 0; i < n; i += 10) {
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
}
对于执行的~14*100=~1400条指令,时空折衷是14行汇编

我们可以进行完全展开,如下所示:

mov eax, 0

loop:

call foo
inc eax
cmp eax, 1000
jne loop
foo();
foo();
// ...
// 996 foo()'s
// ...
foo();
foo();
它在汇编中编译为1000条调用指令

这为1000条指令提供了1000行汇编的时空权衡

如您所见,总的趋势是为了减少CPU执行的指令量,我们必须增加所需的空间

完全展开循环是没有效率的,因为所需的空间变得非常大。部分展开带来了巨大的好处,展开循环越多,回报就会大大减少


虽然理解循环展开是一个好主意,但请记住,编译器是智能的,会为您完成这项工作。

展开循环的过程利用了计算机科学中的一个基本概念:时空权衡,增加使用的空间通常会导致减少算法的时间

假设我们有一个简单的循环

const int n = 1000;

for (int i = 0; i < n; ++i) {
    foo();
}
因此,时空折衷是对执行的~4*1000=~4000条指令进行5行汇编

那么,让我们试着稍微展开这个循环

for (int i = 0; i < n; i += 10) {
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
    foo();
}
对于执行的~14*100=~1400条指令,时空折衷是14行汇编

我们可以进行完全展开,如下所示:

mov eax, 0

loop:

call foo
inc eax
cmp eax, 1000
jne loop
foo();
foo();
// ...
// 996 foo()'s
// ...
foo();
foo();
它在汇编中编译为1000条调用指令

这为1000条指令提供了1000行汇编的时空权衡

如您所见,总的趋势是为了减少CPU执行的指令量,我们必须增加所需的空间

完全展开循环是没有效率的,因为所需的空间变得非常大。部分展开带来了巨大的好处,展开循环越多,回报就会大大减少


虽然理解循环展开是一个好主意,但请记住,编译器是智能的,并且会为您完成这项工作。

我认为澄清什么时候循环展开最有效很重要:使用依赖链。依赖链是一系列操作,其中每个计算都依赖于以前的计算。例如,下面的循环有一个依赖链

for(i=0; i<n; i++) sum += a[i];
没有依赖链,因此无序执行没有问题。但是,让我们考虑一个指令集,它需要每次迭代的以下操作。
2 SIMD load
1 SIMD store
1 SIMD multiply
1 SIMD addition
1 scalar addition for the loop counter
1 conditional jump
我们还假设处理器每个周期可以执行其中五条指令。在这种情况下,每个迭代有七条指令,但每个周期只能执行五条指令。然后可以使用循环展开将标量加法的成本分摊到计数器i和条件跳转中。例如,如果完全展开循环,则不需要这些指令


对于摊销循环计数器和跳跃-funroll循环的成本,GCC可以很好地工作。它展开八次,这意味着计数器加法和跳转必须每八次迭代进行一次,而不是每一次迭代。

我认为澄清什么时候循环展开最有效很重要:使用依赖链。依赖链是一系列操作,其中每个计算都依赖于以前的计算。例如,下面的循环有一个依赖链

for(i=0; i<n; i++) sum += a[i];
2 SIMD load
1 SIMD store
1 SIMD multiply
1 SIMD addition
1 scalar addition for the loop counter
1 conditional jump
没有依赖链,因此无序执行没有问题。但是,让我们考虑一个指令集,它需要每次迭代的以下操作。
2 SIMD load
1 SIMD store
1 SIMD multiply
1 SIMD addition
1 scalar addition for the loop counter
1 conditional jump
我们还假设处理器每个周期可以执行其中五条指令。在这种情况下,每个iter有七条指令 但每个循环只能进行五次。然后可以使用循环展开将标量加法的成本分摊到计数器i和条件跳转中。例如,如果完全展开循环,则不需要这些指令


对于摊销循环计数器和跳跃-funroll循环的成本,GCC可以很好地工作。它展开八次,这意味着计数器的加法和跳转必须每八次迭代进行一次,而不是每一次迭代。

你读过维基百科页面吗?这里有一些很好的c示例。其基本思想是,展开的循环在读取/写入您关心的数据方面花费的周期比检查/更新控制循环的条件变量相关的开销要多,因此可以提供更好的性能:另请参见:您阅读过wikipedia页面吗?这里有一些很好的c示例。基本思想是,展开的循环在读/写您关心的数据时所花费的周期比检查/更新控制循环的条件变量时所花费的开销要多,因此可以提供更好的性能:另请参见:您不应假设编译器将展开循环。在依赖链中,GCC不会展开循环。即使使用-funroll循环,也不会破坏依赖链。手动循环展开在优化中仍然很有用。回答不错。我想知道循环展开是否是大多数编译语言或C&related语言的常见特性。而且,我猜用解释语言展开是不可能的,对吗?很抱歉,如果我在非主题中有点分歧,您不应该假设编译器将展开循环。在依赖链中,GCC不会展开循环。即使使用-funroll循环,也不会破坏依赖链。手动循环展开在优化中仍然很有用。回答不错。我想知道循环展开是否是大多数编译语言或C&related语言的常见特性。而且,我猜用解释语言展开是不可能的,对吗?对不起,如果我在离题的问题上有点分歧
2 SIMD load
1 SIMD store
1 SIMD multiply
1 SIMD addition
1 scalar addition for the loop counter
1 conditional jump