我正在使用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%
}
上述测试应该清楚说明发生了什么。
答案 0 :(得分:3)
您的代码没有任何地方友好性。我抛弃了两个可能的想法。
将unique
和total
组合在一起。
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+1]]
时,stuffs[ids[i]]
可能会被预取。这样可以节省大量的查找时间。
答案 1 :(得分:2)
您可能会遇到别名,导致编译器无法优化您的循环,因为它必须允许unique
,total
和main_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[]
已排序。即使存在重复值,它们也是相邻的,因此您可以通过查找重复值的第一次出现来找到分割点。