What is the overhead in splitting a for-loop into multiple for-loops, if the total work inside is the same?
像这样拆分
1 2 3 4 5 6 7 8 | int i; for (i = 0; i < exchanges; i++) { // some code // some more code // even more code } |
变成这样的多个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | int i; for (i = 0; i < exchanges; i++) { // some code } for (i = 0; i < exchanges; i++) { // some more code } for (i = 0; i < exchanges; i++) { // even more code } |
代码是性能敏感的,但执行后一种操作将显著提高可读性。(在重要的情况下,除了每个循环中的几个访问器外,没有其他循环、变量声明或函数调用。)
我不是一个低级的编程大师,所以如果有人能够比较基本操作来衡量性能的影响,那就更好了,例如,"每个额外的
非常感谢,提前。
经常有太多的因素在起作用…这两种方法都很容易演示:
例如,拆分以下循环会导致几乎2倍的速度减慢(底部是完整的测试代码):
1 2 3 4 5 | for (int c = 0; c < size; c++){ data[c] *= 10; data[c] += 7; data[c] &= 15; } |
这几乎说明了显而易见的一点,因为您需要循环3次而不是一次,并且在整个数组中循环3次而不是1次。
另一方面,如果你看一看这个问题:为什么在单独的循环中元素添加比在组合循环中更快?
1 2 3 4 | for(int j=0;j<n;j++){ a1[j] += b1[j]; c1[j] += d1[j]; } |
由于内存对齐,有时情况正好相反。
从中得到什么?
几乎任何事情都可能发生。这两种方法都不是很快,而且很大程度上取决于循环中的内容。
因此,确定这种优化是否会提高性能通常是一种尝试和错误。有了足够的经验,你可以做出相当有信心(有教养)的猜测。但总的来说,什么都不要期待。
"Each additional for-loop would cost the equivalent of two int allocations."
你说得对,事情没有那么简单。事实上,它是如此复杂,以至于数字意义不大。循环迭代在一个上下文中可能需要x个循环,但在另一个上下文中可能需要y个循环,这是由于许多因素造成的,例如无序执行和数据依赖性。
它不仅依赖于性能上下文,而且还随处理器的不同而变化。
测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #include <time.h> #include <iostream> using namespace std; int main(){ int size = 10000; int *data = new int[size]; clock_t start = clock(); for (int i = 0; i < 1000000; i++){ #ifdef TOGETHER for (int c = 0; c < size; c++){ data[c] *= 10; data[c] += 7; data[c] &= 15; } #else for (int c = 0; c < size; c++){ data[c] *= 10; } for (int c = 0; c < size; c++){ data[c] += 7; } for (int c = 0; c < size; c++){ data[c] &= 15; } #endif } clock_t end = clock(); cout << (double)(end - start) / CLOCKS_PER_SEC << endl; system("pause"); } |
输出(一个回路):4.08秒输出(3圈):7.17秒
处理器喜欢使用更高比率的数据指令来跳转指令。分支指令可能会强制处理器清除指令管道并重新加载。
基于指令管道的重新加载,第一种方法会更快,但不会显著。您可以通过拆分添加至少2个新的分支指令。
更快的优化是展开循环。展开循环试图提高数据指令与分支指令的比率,方法是在分支到循环顶部之前在循环内部执行更多指令。
另一个重要的性能优化是组织数据,使其适合处理器的一条缓存线。例如,您可以拆分内部循环来处理单个数据缓存,外部循环会将新项目加载到缓存中。
只有在程序正确、可靠地运行并且环境要求更高的性能之后,才能应用此优化。定义为观察者(动画/电影)、用户(等待响应)或硬件(在关键时间事件之前执行操作)的环境。任何其他目的都是浪费您的时间,因为操作系统(运行并发程序)和存储访问将对您的程序的性能问题做出更多贡献。
这将很好地指示一个版本是否比另一个版本快。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | #include #include <chrono> #include <iostream> #include <numeric> #include <string> const int iterations = 100; namespace { const int exchanges = 200; template<typename TTest> void Test(const std::string &name, TTest &&test) { typedef std::chrono::high_resolution_clock Clock; typedef std::chrono::duration<float, std::milli> ms; std::array<float, iterations> timings; for (auto i = 0; i != iterations; ++i) { auto t0 = Clock::now(); test(); timings[i] = ms(Clock::now() - t0).count(); } auto avg = std::accumulate(timings.begin(), timings.end(), 0) / iterations; std::cout <<"Average time," << name <<":" << avg << std::endl; } } int main() { Test("single loop", []() { for (auto i = 0; i < exchanges; ++i) { // some code // some more code // even more code } }); Test("separated loops", []() { for (auto i = 0; i < exchanges; ++i) { // some code } for (auto i = 0; i < exchanges; ++i) { // some more code } for (auto i = 0; i < exchanges; ++i) { // even more code } }); } |
事情很简单。第一个代码类似于在赛道上跑一圈,另一个代码类似于进行一次完整的3圈比赛。所以,三圈比一圈需要更多的时间。但是,如果循环正在做一些需要按顺序完成的事情,并且它们相互依赖,那么第二个代码将完成这些事情。例如,如果第一个循环正在进行一些计算,而第二个循环正在对这些计算进行一些工作,那么两个循环都需要按顺序进行,否则就不需要……