c++优化简单循环

C++ optimize simple loop

本文关键字:单循环 简单 优化 c++      更新时间:2023-10-16

我正在使用Visual Studio 2012并在x64发布模式上构建。下面的代码占用了我的程序运行所需时间的33.5%。我使用visual studio分析器来测量它。

    //every variable is unsigned int or unsigned int*
    for(unsigned int i = 0; i < num; i++)
    {
        unique[ids[i]]++;//2.1%
        total[ids[i]] += main_list[id];//31.4%
    }

有人可以推荐一种方法来减少这个函数需要运行的时间吗?

编辑:根据您的输入,我尝试了以下代码:

    const unsigned int now = main_list[id];
    for(unsigned int i = ids[0], j = 0; j < num; j++)
    {
        ++unique[i];//2.0%
        total[i] += now;//16.7%
        i = ids[j];//16.8%
    }

这证实了可能CPU分支预测失败的理论,因为位置是随机的(顺便说一句,它们不是完全随机的,而是排序的)。有什么办法可以加快我的代码吗?

第二次编辑:我尝试了以下内容:

    const unsigned int now = main_list[id];
    for(unsigned int i = ids[0], j = 0; j < num; j++)
    {
        total[i] += now;//2.0%
        ++unique[i];//16.7%
        i = ids[j];//16.8%
    }

您的代码没有得到任何本地友好性。我会提出两个可能的想法。

  1. uniquetotal组在一起。

    struct Stuff {
        unsigned int unique, total;
    };
    for(unsigned int i = 0; i < num; i++)
    {
        Stuff& s = stuffs[ids[i]];
        s.unique++;
        s.total += main_list[id]; // <== is this supposed to be ids[i]?
    }
    

这将确保你在内存中连续访问的东西实际上在内存中彼此相邻。实际上,假设num足够大,每一行都有缓存丢失。这就是你能得到的最糟糕的了。

  • 排序ids。现在,你还在记忆里跳来跳去。让我们来确保我们可以按顺序执行:

    std::sort(ids, ids + num);
    // rest of loop as before
    
  • 这样,很可能在处理stuffs[ids[i]]时预取stuffs[ids[i+1]]

    您可能会受到混叠的影响,因为它必须允许unique, totalmain_list在内存中重叠的可能性,从而阻止编译器在这里优化循环。

    const auto mainListId = main_list[id];
    for (unsigned int i = 0; i < num; ++i) {
        const auto currId = ids[i];
        ++unique[currId];
        total[currId] += mainListId;
    }
    

    当然,假设实际上没有任何混叠发生

    对于这样一个简单的循环,你不能做更多的事情。您可以确保将编译器优化设置设置为最大值,如果编译器没有为您执行循环,您可以尝试展开循环。除此之外,您可能需要在这里展示的代码范围之外进行算法改进。

    由于ids的排序导致的非顺序内存访问,您可能存在内存绑定。这也许可以通过在此循环之前对ids数组进行排序来解决,但如果没有更多的上下文来说明您正在尝试做的事情,很难说这是否有意义。

    我对i = ids[j]; //16.8%感到惊讶-这应该更快。看来时机不对了。++unique[i]; //2.0%是一个非线性(不可预取)访问,应该更慢,而不是快8倍。事实上,ids[]应该在缓存中,所以你只有八分之一的访问到达主存。语句应该比快8倍。你确定时机对,行动对吗?

    也就是说,应该并行化循环。这不会有多大帮助;主存不会变快。但是你应该让主存储器忙起来。CPU预取器的思想是,如果没有显式访问,则抛出一些预测访问。如果预测是正确的,它节省了时间,否则它只是浪费了一点能量。

    并行循环是可能的,因为ids[]是排序的。即使存在重复值,它们也是相邻的,因此您可以通过查找重复值的第一次出现来找到分割点。