C 集体循环还是单独循环以获得最佳性能?
假设我有C 集体循环还是单独循环以获得最佳性能?,c,arrays,performance,loops,caching,C,Arrays,Performance,Loops,Caching,假设我有N在C语言中大小相同的连续数组(或任何其他语言-我想这没什么关系)。我想在这些数组上循环,并对每个数组元素执行一些操作。这可以通过单个循环实现,因为所有N数组的大小都相同。但是,由于计算机内存的工作方式,对每个阵列执行单独的循环是否更快 具体来说,假设N非常大,可能有几十亿个,使得每个数组的大小都有很多GB。此外,数组实际上是3D的,这意味着完整的“数组上的循环”实际上是三个嵌套的循环。从三个循环变量计算数组指针的算法的复杂性与数组元素上实际操作的复杂性相当,这就是为什么我害怕添加更多的
N
在C语言中大小相同的连续数组(或任何其他语言-我想这没什么关系)。我想在这些数组上循环,并对每个数组元素执行一些操作。这可以通过单个循环实现,因为所有N
数组的大小都相同。但是,由于计算机内存的工作方式,对每个阵列执行单独的循环是否更快
具体来说,假设N
非常大,可能有几十亿个,使得每个数组的大小都有很多GB。此外,数组实际上是3D的,这意味着完整的“数组上的循环”实际上是三个嵌套的循环。从三个循环变量计算数组指针的算法的复杂性与数组元素上实际操作的复杂性相当,这就是为什么我害怕添加更多的循环
“显而易见”的答案是只编写这两个程序,看看哪一个在我的特定情况下表现最好。然而,我想听听关于如何判断这种情况的更为深思熟虑的论据/指导方针,因为这超出了我自己的编程直觉。我最初的直觉是,每个数组循环会更好,因为数据局部性将有助于缓存 将所有数组放在一个循环中会污染缓存(在处理第一个数组时,缓存将(部分)由该数组的某些元素填充,但在下一行代码中,您将需要第二个数组的数据,而缓存将无法提供帮助) 在这两种情况下,理论复杂性保持不变
但是,这取决于您拥有的数组的数量(我现在不是说连续数组)。必须从头开始一次又一次地循环可能会超过每个循环一个阵列的方法所能提供的缓存加速,如下所示:
Georgioss-MacBook-Pro:~ gsamaras$ cat bigloop.c
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#define N 100000
typedef struct timeval wallclock_t;
void wallclock_mark(wallclock_t *const tptr);
double wallclock_since(wallclock_t *const tptr);
// gcc -Wall -O3 bigloop.c -o bigloop
int main(void)
{
int a[N], b[N], c[N], d[N], e[N], i;
wallclock_t t;
double s;
wallclock_mark(&t);
for(i = 0; i < N; ++i)
{
a[i] = i * 10 + (i - 2);
b[i] = i * 9 + (i - 3);
c[i] = i * 8 + (i - 1);
d[i] = i * 11 + (i - 5);
e[i] = i * 5 + (i - 0);
}
s = wallclock_since(&t);
printf("Populating took %.9f seconds wall clock time.\n", s);
wallclock_mark(&t);
for(i = 0; i < N; ++i)
{
a[i] = e[i] + (i - 1);
b[i] = d[i] + (i + 3);
c[i] = a[i] - (i + 2);
d[i] = b[i] + (i - 2);
e[i] = a[i] + (i - 4);
}
s = wallclock_since(&t);
printf("Load/write took %.9f seconds wall clock time.\n", s);
return 0;
}
#include <stdio.h>
#include <sys/time.h>
#include <time.h>
#define N 100000
typedef struct timeval wallclock_t;
void wallclock_mark(wallclock_t *const tptr);
double wallclock_since(wallclock_t *const tptr);
int main(void)
{
int a[N], b[N], c[N], d[N], e[N], i;
wallclock_t t;
double s;
wallclock_mark(&t);
for(i = 0; i < N; ++i)
a[i] = i * 10 + (i - 2);
for(i = 0; i < N; ++i)
b[i] = i * 9 + (i - 3);
for(i = 0; i < N; ++i)
c[i] = i * 8 + (i - 1);
for(i = 0; i < N; ++i)
d[i] = i * 11 + (i - 5);
for(i = 0; i < N; ++i)
e[i] = i * 5 + (i - 0);
s = wallclock_since(&t);
printf("Populating took %.9f seconds wall clock time.\n", s);
wallclock_mark(&t);
for(i = 0; i < N; ++i)
a[i] = e[i] + (i - 1);
for(i = 0; i < N; ++i)
b[i] = d[i] + (i + 3);
for(i = 0; i < N; ++i)
c[i] = a[i] - (i + 2);
for(i = 0; i < N; ++i)
d[i] = b[i] + (i - 2);
for(i = 0; i < N; ++i)
e[i] = a[i] + (i - 4);
s = wallclock_since(&t);
printf("Load/write took %.9f seconds wall clock time.\n", s);
return 0;
}
在这里,您可以看到在填充数组时,一个循环中的所有数组都会得到一个数量级的加速。而且,它处理它们的速度也更快
因此,如果我是你,我将实施这两种方法并测量时间!=)
注:不要成为过早优化的受害者。如果您有一个想要优化的项目,请分析您的代码以找到瓶颈并专注于此 我看到你没有反汇编代码-否则你会看到-O3->所有循环都作为死代码删除后发生了什么。所以你的代码证明不了什么。谢谢你的评论@gsamaras“一个数量级的加速”?更像是二的因素。因此,使用单个循环可以获得最佳性能,但这与假定的缓存加速相反?@Anty“所有循环都作为死代码删除”是什么意思?你的意思是-O3将许多循环编译成一个大循环吗?在这种情况下,性能差异从何而来?否-这意味着它们被完全删除,因为a[N]、b[N]、c[N]、d[N]和e[N]从未作为任何具有副作用的表达式的一部分使用。将它们输出到控制台以禁止这种效果就足够了。使用objdump或锁紧螺栓检查总成。
Georgioss-MacBook-Pro:~ gsamaras$ ./bigloop
Populating took 0.000581000 seconds wall clock time.
Load/write took 0.000178000 seconds wall clock time.
Georgioss-MacBook-Pro:~ gsamaras$ ./loop
Populating took 0.001092000 seconds wall clock time.
Load/write took 0.000285000 seconds wall clock time.