C ++优化简单循环

时间:2015-09-02 19:56:06

标签: c++ optimization

我正在使用Visual Studio 2012并在x64发布模式下构建。以下代码占我的程序运行时间的33.5%。我使用visual studio profiler来测量它。

    //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%
    }

上述测试应该清楚说明发生了什么。

3 个答案:

答案 0 :(得分:3)

您的代码没有任何地方友好性。我抛弃了两个可能的想法。

  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]?
    }
    
  2. 这将确保您在内存中连续访问的内容实际上在内存中彼此相邻。原样,假设num足够大,您就会在每一行上缓存缺失。那差不多就像你能得到的一样糟糕。

    1. 排序ids。现在,你仍然在记忆中蹦蹦跳跳。让我们确保我们实际上可以按顺序进行:

      std::sort(ids, ids + num);
      // rest of loop as before
      
    2. 这样,在您处理stuffs[ids[i+1]]时,stuffs[ids[i]]可能会被预取。这样可以节省大量的查找时间。

答案 1 :(得分:2)

您可能会遇到别名,导致编译器无法优化您的循环,因为它必须允许uniquetotalmain_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数组进行排序来解决,但如果没有更多的上下文,那么很难说这是否合理。

答案 2 :(得分:1)

我对i = ids[j]; //16.8%感到惊讶 - 应该更快。看起来时机已关闭。 ++unique[i]; //2.0%是非线性(非预取)访问,应该更慢,而不是8倍。事实上,ids[]应该在缓存中,因此您只有八分之一的访问权限在主内存中。该陈述应该是更快的8倍。你确定你有合适的时间进行正确的操作吗?

那就是说,你应该并行化循环。它没有多大帮助;主存不会变得更快。但你应该保持主内存忙。如果没有显式访问,CPU预取器的想法是抛出一些预测的访问。如果预测是正确的,它可以节省时间,否则它只会浪费一些能量。

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