如果内部的总工作量相同,那么将for循环拆分为多个for循环的开销是多少



像这样分割for循环的开销是多少,

int i;
for (i = 0; i < exchanges; i++)
{
// some code
// some more code
// even more code
}

变成多个像这样的for-循环?

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
}

代码对性能敏感,但执行后者将显著提高可读性。(如果重要的话,在每个循环中除了几个访问器之外,没有其他循环、变量声明或函数调用。)

我并不是一个低级编程大师,所以如果有人能衡量与基本操作相比的性能打击,那就更好了,例如"每个额外的for-循环将花费相当于两个int分配的成本。"但是,如果不是那么简单,我理解(也不会感到惊讶)。

非常感谢,提前。

通常有太多因素在起作用。。。这两种方式都很容易演示:

例如,分割以下循环会导致几乎2倍的减速(底部的完整测试代码):

for (int c = 0; c < size; c++){
data[c] *= 10;
data[c] += 7;
data[c] &= 15;
}

这几乎说明了显而易见的一点,因为你需要循环3次,而不是一次,并且你在整个数组上进行3次而不是1次。

另一方面,如果你看看这个问题:为什么在单独的循环中元素添加比在组合的循环中快得多?

for(int j=0;j<n;j++){
a1[j] += b1[j];
c1[j] += d1[j];
}

由于内存对齐,有时情况正好相反。


从中得到什么

几乎任何事情都有可能发生。这两种方式都不总是更快,而且在很大程度上取决于循环内部的内容。

因此,确定这样的优化是否会提高性能通常是反复试验的。有了足够的经验,你可以做出相当自信(有根据)的猜测。但总的来说,什么都可以期待。

"每个额外的for循环将花费相当于两个int分配的成本。">

你说得对,事情没那么简单。事实上,它是如此复杂,以至于这些数字没有多大意义。循环迭代在一个上下文中可能需要X个循环,但由于诸如无序执行和数据依赖性等多种因素,在另一个上下文中将需要Y个循环。

不仅性能上下文相关,而且不同的处理器也会有所不同。


这是测试代码:

#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条新的分支指令。

更快的优化是展开循环。展开循环试图通过在分支到循环顶部之前在循环内执行更多指令来提高数据指令与分支指令的比率。

另一个重要的性能优化是组织数据,使其适合处理器的缓存线之一。例如,您可以拆分处理单个数据缓存的内部循环,而外部循环则将新项目加载到缓存中。

只有在程序正确且稳健地运行环境要求更高的性能之后,才能应用此优化。定义为观察者(动画/电影)、用户(等待响应)或硬件(在关键时间事件之前执行操作)的环境。任何其他目的都是浪费时间,因为操作系统(运行并发程序)和存储访问会对程序的性能问题造成更大的影响。

这将很好地指示一个版本是否比另一个版本更快。

#include <array>
#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圈比赛。因此,三圈比一圈需要更多的时间。然而,如果循环正在做一些需要按顺序完成的事情,并且它们相互依赖,那么第二个代码将完成这些事情。例如,如果第一个循环正在进行一些计算,而第二个循环正在对这些计算进行一些运算,那么两个循环都需要按顺序进行,否则就不能。。。

最新更新