【发布时间】:2009-05-25 00:31:04
【问题描述】:
我一直在从事一个项目,并试图找到执行时间大幅放缓的根源,并将其缩小到我设法从逻辑中优化的单一方法。问题是我的解决方案涉及使用引用,这使得代码的另一部分运行得非常缓慢......我想回答的问题是为什么当地图是引用而不是引用时,内部循环需要更长的时间来评估局部变量?
这是优化之前的旧方法:
// old method: create an empty map, populate it
// and then assign it back to the path object later
map<int,float> screenline_usage;
for (int i=0; i<numCandidates; ++i)
{
// timing starts here.
map<int, float>& my_screenline_usage =
path->get_combined_screenline_usage(legnr, stop_id);
map<int, float>::iterator it = my_screenline_usage.begin();
for (; it != my_screenline_usage.end(); ++it)
screenline_usage[it->first] += usage * it->second;
// timing ends here, this block evaluated 4 million times for overall execution time of ~12 seconds
}
// This function call is evaluated 400k times for an overall execution time of ~126 seconds
path->set_zone_screenline_usage(access_mode, zone_id, screenline_usage);
// TOTAL EXECUTION TIME: 138 seconds.
优化后的新方式:
// new method: get a reference to internal path mapping and populate it
map<int, float>& screenline_usage =
path->get_zone_screenline_usage(access_mode, zone_id);
screenline_usage.clear();
for (int i=0; i<numCandidates; ++i)
{
// timing starts here
map<int, float>& my_screenline_usage =
path->get_combined_screenline_usage(legnr, stop_id);
map<int, float>::iterator it = my_screenline_usage.begin();
for (; it != my_screenline_usage.end(); ++it)
screenline_usage[it->first] += usage * it->second;
// timing ends here, this block evaluated 4 million times for overall execution time of ~76 seconds
}
// New method... no need to assign back to path object (0 seconds execution :)
// TOTAL EXECUTION TIME: 76 seconds (62 second time saving) ... but should be able to do it in just 12 seconds if the use of reference didn't add so much time :(
以下是从该代码调用的相关子例程:
// This is the really slow routine, due to the copy assignment used.
void set_zone_screenline_usage(int access_mode, int zone_id,
map<int,float>& screenline_usage)
{
m_container[access_mode][zone_id] = screenline_usage;
}
map<int,float>& get_zone_screenline_usage(int access_mode, int zone_id)
{
return m_container[access_mode][zone_id];
}
注意:计时信息适用于单次运行,其中上述代码被评估大约 400k 次。计时是使用我为访问 RDTSC 时间戳计数器而构建的一些类完成的(是的,我知道 TSC 表示时间戳计数器),numCandidates 的平均值为 10,放入 screenline_usage 映射的平均元素数为 25。
更新:首先感谢所有参与其中的人。我认为最终这与 C++ 引用完全无关,更多的是与缓存一致性有关。我已经用一个vector&和一个实现为成员变量map的散列函数替换了上面的优化代码
// newest method: get a reference to internal path mapping (as vector) and populate it
// map<int,int> m_linkNum_to_SlNum declared in header and populated in constructor.
vector<float>& screenline_usage = path->get_zone_screenline_usage(access_mode, zone_id);
for (int i=0; i<numCandidates; ++i)
{
// timing starts here
map<int, float>& my_screenline_usage =
path->get_combined_screenline_usage(legnr, stop_id);
map<int, float>::iterator it = my_screenline_usage.begin();
for (; it != my_screenline_usage.end(); ++it)
screenline_usage[m_linkNum_to_SlNum[it->first]] += usage * it->second;
// timing ends here, this block evaluated 4 million times for overall execution time of ~9 seconds
}
// Newest method... again no need to assign back to path object (0 seconds execution :)
// TOTAL EXECUTION TIME: just 9 seconds (129 second time saving) ... this is even better than using a locally constructed map which took 12 seconds in the inner loop :)
在我看来,鉴于向量不是本地的,而是一个连续的内存块,并且散列函数 (m_linkNum_to_SlNum) 是本地成员变量,这种方法导致代码/数据能够适合缓存,而不必去主内存获取数据,从而显着提高速度。非常感谢根据这些发现得出的其他结论。
【问题讨论】:
-
你必须整理一下——旧方法需要 12 秒还是 126 秒?您是在谈论传递参考还是返回参考?
-
如果不是分配 screenline_usage 参数(此后无论如何都没用),您将使用旧方法获得更好的计时,而是将其与成员交换:screenline_usage.swap(get_zone_screenline_usage(access_mode, zone_id)) ;
-
12s 是所有 400K 运行的总和,还是所有 400K 运行的平均值?你如何衡量?
-
我们不是编译器,我们不需要#ifdefs。只需将两个版本并排展示给我们,作为单独的实现。没有交错和混合在一起。
-
为什么在 i 甚至没有被使用的时候迭代它?这可能会无缘无故地花费大量时间。将 usage * it->second 与 numCandidates 相乘并将其添加到地图中。希望你明白我的意思。
标签: c++ optimization reference map timing