8

for像这样拆分 -loop的开销是多少,

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的 -loop 将花费相当于两次int分配的成本。” 但是,如果不是那么简单,我理解(并且不会感到惊讶)。

提前谢谢了。

4

4 回答 4

14

通常有太多因素在起作用......而且很容易证明两种方式:

例如,拆分以下循环会导致几乎 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 秒

于 2012-12-13T20:00:12.733 回答
4

处理器更喜欢数据指令与跳转指令的比例更高。
分支指令可能会强制您的处理器清除指令流水线并重新加载。

基于指令流水线的重新加载,第一种方法会更快,但并不显着。您将通过拆分添加至少 2 个新的分支指令。

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

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

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

于 2012-12-13T19:48:51.680 回答
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
            }
        });
}
于 2012-12-13T21:23:31.940 回答
-3

事情很简单。第一个代码就像在赛道上跑了一圈,另一个代码就像跑了整整 3 圈。因此,跑三圈而不是一圈需要更多的时间。但是,如果循环正在做一些需要按顺序完成的事情并且它们相互依赖,那么第二个代码将做这些事情。例如,如果第一个循环正在做一些计算,而第二个循环正在对这些计算做一些工作,那么两个循环都需要按顺序完成,否则不会......

于 2012-12-13T19:51:05.803 回答