C++ 为了提高循环效率:合并循环
我一直认为减少迭代次数是提高程序效率的方法。因为我从未真正证实过这一点,所以我开始测试这一点C++ 为了提高循环效率:合并循环,c++,performance,loops,benchmarking,C++,Performance,Loops,Benchmarking,我一直认为减少迭代次数是提高程序效率的方法。因为我从未真正证实过这一点,所以我开始测试这一点 我做了以下C++程序,测量两个不同函数的时间: 第一个函数执行单个大循环并使用一组变量 第二个函数执行多个同样大的循环,但每个变量只有一个循环 完整的测试代码: #include <iostream> #include <chrono> using namespace std; int* list1; int* list2; int
我做了以下C++程序,测量两个不同函数的时间:
- 第一个函数执行单个大循环并使用一组变量
- 第二个函数执行多个同样大的循环,但每个变量只有一个循环
#include <iostream>
#include <chrono>
using namespace std;
int* list1; int* list2;
int* list3; int* list4;
int* list5; int* list6;
int* list7; int* list8;
int* list9; int* list10;
const int n = 1e7;
// **************************************
void myFunc1()
{
for (int i = 0; i < n; i++)
{
list1[i] = 2;
list2[i] = 4;
list3[i] = 8;
list4[i] = 16;
list5[i] = 32;
list6[i] = 64;
list7[i] = 128;
list8[i] = 256;
list9[i] = 512;
list10[i] = 1024;
}
return;
}
// **************************************
void myFunc2()
{
for (int i = 0; i < n; i++)
{
list1[i] = 2;
}
for (int i = 0; i < n; i++)
{
list2[i] = 4;
}
for (int i = 0; i < n; i++)
{
list3[i] = 8;
}
for (int i = 0; i < n; i++)
{
list4[i] = 16;
}
for (int i = 0; i < n; i++)
{
list5[i] = 32;
}
for (int i = 0; i < n; i++)
{
list6[i] = 64;
}
for (int i = 0; i < n; i++)
{
list7[i] = 128;
}
for (int i = 0; i < n; i++)
{
list8[i] = 256;
}
for (int i = 0; i < n; i++)
{
list9[i] = 512;
}
for (int i = 0; i < n; i++)
{
list10[i] = 1024;
}
return;
}
// **************************************
int main()
{
list1 = new int[n]; list2 = new int[n];
list3 = new int[n]; list4 = new int[n];
list5 = new int[n]; list6 = new int[n];
list7 = new int[n]; list8 = new int[n];
list9 = new int[n]; list10 = new int[n];
auto start = chrono::high_resolution_clock::now();
myFunc1();
auto elapsed = chrono::high_resolution_clock::now() - start;
long long microseconds = chrono::duration_cast<chrono::microseconds>(elapsed).count();
cout << "Time taken by func1 (micro s):" << microseconds << endl << endl;
//
start = chrono::high_resolution_clock::now();
myFunc2();
elapsed = chrono::high_resolution_clock::now() - start;
microseconds = chrono::duration_cast<chrono::microseconds>(elapsed).count();
cout << "Time taken by func2 (micro s):" << microseconds << endl << endl;
delete[] list1; delete[] list2; delete[] list3; delete[] list4;
delete[] list5; delete[] list6; delete[] list7; delete[] list8;
delete[] list9; delete[] list10;
return 0;
}
#包括
#包括
使用名称空间std;
int*list1;int*list2;
int*list3;int*list4;
int*清单5;int*清单6;
int*清单7;int*list8;
int*list9;int*清单10;
常数int n=1e7;
// **************************************
void myFunc1()
{
对于(int i=0;i 你从一个循环中得到的东西是,你失去了循环变量的递增。因此,在这种情况下,循环的内容非常简单,赋值(和测试)会产生很大的不同
您的示例也没有考虑到的是,连续内存访问通常比随机访问更快
在一个循环需要更长时间的函数中(试着加入睡眠而不是赋值),你会发现差异并不是很大
提高性能的方法是从数学开始——正确的算法总是能买到最大的改进。理想情况下,这是在手指敲击键盘之前完成的。如果我不得不冒险猜测,我会说,您看到的是第一个函数中更频繁的内存缓存未命中的结果
myFunc1()
基本上是以随机访问方式执行10e8内存写入
myFunc2()
正在执行10e7个字的10x顺序内存写入
在现代内存体系结构上,我希望第二种更有效。此代码创建变量:
list1 = new int[n]; list2 = new int[n];
list3 = new int[n]; list4 = new int[n];
list5 = new int[n]; list6 = new int[n];
list7 = new int[n]; list8 = new int[n];
list9 = new int[n]; list10 = new int[n];
但它几乎肯定不会创建实际的物理页映射,直到实际修改内存
因此,您的func1()
必须等待RAM的实际物理页的创建,而func2()
则没有。更改顺序,映射时间将归因于func2()
性能
对于发布的代码,最简单的解决方案是在执行定时运行之前运行func1()
或func2()
如果在进行任何基准测试之前未确保实际物理内存已映射,则该映射将是您首次修改内存时测量的时间的一部分。尝试对代码进行基准测试时,您需要:
在启用优化标志的情况下编译
多次运行每个测试,以收集平均值
您没有同时执行这两项操作。例如,您可以使用-O3
,对于平均值,我这样做了(我让函数从列表中返回一个元素):
这证实了你所看到的,但区别是一个数量级(这是一个非常大的问题)
在单for循环中,您执行一次内务处理,循环的计数器增加一次。在多个for循环中,这是扩展的(您需要执行的次数与for循环的次数相同)。当循环体有点琐碎时,就像在您的例子中一样,那么它可以产生不同
另一个问题是数据局部性。第二个函数的循环一次将填充一个列表(意味着内存将以连续方式访问)。在第一个函数的大循环中,您将一次填充列表中的一个元素,这归结为内存的随机访问(例如,list1
将被带到缓存中,因为您填充了其中的一个元素,因此在代码的下一行中,您将请求list2
,这意味着list1
现在是无用的。但是,在第二个函数中,一旦您将list1
带到缓存中,您将从
for(int i = 0; i < 100; ++i)
dummy = myFunc1();
Time taken by func1 (micro s):206693
Time taken by func2 (micro s):37898
// global modifier allows auto-vec of myFunc1
#define GLOBAL_MODIFIER __restrict
#define LOCAL_MODIFIER __restrict // inside myFunc1
static int *GLOBAL_MODIFIER list1, *GLOBAL_MODIFIER list2,
*GLOBAL_MODIFIER list3, *GLOBAL_MODIFIER list4,
*GLOBAL_MODIFIER list5, *GLOBAL_MODIFIER list6,
*GLOBAL_MODIFIER list7, *GLOBAL_MODIFIER list8,
*GLOBAL_MODIFIER list9, *GLOBAL_MODIFIER list10;
.L12: # myFunc1 inner loop from gcc8.1 -O3 with __restrict pointers
movups XMMWORD PTR [rbp+0+rax], xmm9 # MEM[base: l1_16, index: ivtmp.87_52, offset: 0B], tmp108
movups XMMWORD PTR [rbx+rax], xmm8 # MEM[base: l2_17, index: ivtmp.87_52, offset: 0B], tmp109
movups XMMWORD PTR [r11+rax], xmm7 # MEM[base: l3_18, index: ivtmp.87_52, offset: 0B], tmp110
movups XMMWORD PTR [r10+rax], xmm6 # MEM[base: l4_19, index: ivtmp.87_52, offset: 0B], tmp111
movups XMMWORD PTR [r9+rax], xmm5 # MEM[base: l5_20, index: ivtmp.87_52, offset: 0B], tmp112
movups XMMWORD PTR [r8+rax], xmm4 # MEM[base: l6_21, index: ivtmp.87_52, offset: 0B], tmp113
movups XMMWORD PTR [rdi+rax], xmm3 # MEM[base: l7_22, index: ivtmp.87_52, offset: 0B], tmp114
movups XMMWORD PTR [rsi+rax], xmm2 # MEM[base: l8_23, index: ivtmp.87_52, offset: 0B], tmp115
movups XMMWORD PTR [rcx+rax], xmm1 # MEM[base: l9_24, index: ivtmp.87_52, offset: 0B], tmp116
movups XMMWORD PTR [rdx+rax], xmm0 # MEM[base: l10_25, index: ivtmp.87_52, offset: 0B], tmp117
add rax, 16 # ivtmp.87,
cmp rax, 40000000 # ivtmp.87,
jne .L12 #,