矢量性能受苦
Vector performance suffering
我一直在研究状态空间探索,最初使用映射来存储世界状态的赋值,如map<Variable *, int>
,其中变量是世界中的域从0到n的对象,其中n是有限的。实现的性能非常快,但我注意到它不能很好地随状态空间的大小而扩展。我将状态更改为使用 vector<int>
,其中我使用变量的 id 在向量中查找其索引。内存使用量大大提高,但求解器的效率下降(从<30秒下降到400+(。我修改的唯一代码是生成状态并验证状态是否是目标。我不知道为什么使用矢量会降低性能,特别是因为矢量运算在最坏的情况下应该只花费线性时间。
最初这就是我生成节点的方式:
State * SuccessorGen::generate_successor(const Operator &op, map<Variable *, int> &var_assignment){
map<Variable *, int> values;
values.insert(var_assignment.begin(), var_assignment.end());
vector<Operator::Effect> effect = op.get_effect();
vector<Operator::Effect>::const_iterator eff_it = effect.begin();
for (; eff_it != effect.end(); eff_it++){
values[eff_it->var] = eff_it->after;
}
return new State(values);
}
在我的新实现中:
State* SuccessorGen::generate_successor(const Operator &op, const vector<int> &assignment){
vector<int> child;
child = assignment;
vector<Operator::Effect> effect = op.get_effect();
vector<Operator::Effect>::const_iterator eff_it = effect.begin();
for (; eff_it != effect.end(); eff_it++){
Variable *v = eff_it->var;
int id = v->get_id();
child[id] = eff_it->after;
}
return new State(child);
}
(目标检查类似,只是循环访问目标分配而不是运算符效果。
这些向量运算真的比使用地图慢得多吗?我可以使用开销较低的同样高效的 STL 容器吗?变量的数量相对较小(<50(,并且在 for 循环之后永远不需要调整向量的大小或修改。
编辑:
我尝试对所有运算符进行一个循环计时以查看时序比较,使用效果列表和分配,矢量版本在 0.3 秒内运行一个循环,而地图版本略高于 0.4 秒。当我注释掉那部分时,地图大致相同,但矢量跃升至接近 0.5 秒。我添加了child.reserve(assignment.size())
但没有进行任何更改。
编辑 2:
从user63710的答案中,我也一直在挖掘其余的代码,并注意到启发式计算中发生了一些非常奇怪的事情。矢量版本工作正常,但对于地图,我Node *n = new Node(i, transition.value, label_cost); open_list.push(n);
使用此行,但是一旦循环完成填充队列,节点就会完全搞砸。节点是一个简单的结构,如下所示:
struct Node{
// Source Value, Destination Value
int from;
int to;
int distance;
Node(int &f, int &t, int &d) : from(f), to(t), distance(d){}
};
它没有from, to, distance
,而是用一些随机数用id
替换from
和to
,并且搜索不会做它应该做的事情,并且返回的速度比它应该快得多。当我调整地图版本以将地图转换为矢量并运行以下命令时:
Node n(i, transition.value, label_cost); open_list.push(n);
性能大致等于矢量的性能。所以这解决了我的主要问题,但这让我想知道为什么使用 Node *n
会得到这种行为与Node n()
相反?
如果如您所说,这些结构的大小相当小(~50 个元素(,我不得不认为问题出在其他地方。至少,我认为它不涉及向量/映射的内存访问或分配。
我为测试而制作的一些示例代码: 地图版本:
unique_ptr<map<int, int>> make_successor_map(const vector<int> &ids,
const map<int, int> &input)
{
auto new_map = make_unique<map<int, int>>(input.begin(), input.end());
for (size_t i = 0; i < ids.size(); ++i)
swap((*new_map)[ids[i]], (*new_map)[i]);
return new_map;
}
int main()
{
auto a_map = make_unique<map<int, int>>();
// ids to access
vector<int> ids;
const int n = 100;
for (int i = 0; i < n; ++i)
{
a_map->insert({i, rand()});
ids.push_back(i);
}
random_shuffle(ids.begin(), ids.end());
for (int i = 0; i < 1e6; ++i)
{
auto temp_map = make_successor_map(ids, *a_map);
swap(temp_map, a_map);
}
cout << a_map->begin()->second << endl;
}
矢量版本:
unique_ptr<vector<int>> make_successor_vec(const vector<int> &ids,
const vector<int> &input)
{
auto new_vec = make_unique<vector<int>>(input);
for (size_t i = 0; i < ids.size(); ++i)
swap((*new_vec)[ids[i]], (*new_vec)[i]);
return new_vec;
}
int main()
{
auto a_vec = make_unique<vector<int>>();
// ids to access
vector<int> ids;
const int n = 100;
for (int i = 0; i < n; ++i)
{
a_vec->push_back(rand());
ids.push_back(i);
}
random_shuffle(ids.begin(), ids.end());
for (int i = 0; i < 1e6; ++i)
{
auto temp_vec = make_successor_vec(ids, *a_vec);
swap(temp_vec, a_vec);
}
cout << *a_vec->begin() << endl;
}
地图版本在我的旧酷睿 2 Duo T9600 上运行大约需要 15 秒,矢量版本需要 0.406 秒。我们都是在 G++ 4.9.2 上编译的,带有 g++ -O3 --std=c++1y
.因此,如果您的代码每次迭代需要 0.4 秒(请注意,我的示例代码 0.4 秒用于 100 万次调用(,那么我真的认为您的问题出在其他地方。
这并不是说您不会因为从 map->vector 切换而导致性能下降,而是您发布的代码并没有显示太多原因
问题是您在不保留空间的情况下创建向量。向量连续存储元素。 这确保了对元素的持续访问。
因此,每次向矢量添加项目时(例如通过插入器(,矢量都必须重新分配更多空间,并最终将所有现有元素移动到重新分配的内存位置。 这会导致速度变慢和大量堆碎片。
解决方案是reserve()
元素,如果您事先知道您将拥有多少元素。 或者,如果您不保留((较大的块并比较size()
和capacity()
以检查是否该保留更多块。
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- OpenMP阵列性能较差
- 递归列出所有目录中的C++与Python与Ruby的性能
- 大小相等但成员数量不同的结构之间的性能差异
- 为什么constexpr的性能比正常表达式差
- 在类中使用随机生成器时出现性能问题
- 在main()之外初始化std::vector会导致性能下降(多线程)
- 海湾合作委员会 ARM 性能下降
- GCC 和 Clang 代码性能的巨大差异
- 在容量内调整矢量大小时的性能影响
- 了解算法的性能差异(如果以不同的编程语言实现)
- 未达到的情况会影响开关外壳性能
- QStringList vs list<shared_ptr<QString>> 性能比较C++
- 是否总是可以将使用递归编写的程序重写为不使用递归的程序C++,性能观点是什么?
- 哪种方法更好,性能明智
- C++ 特征库:引用的性能开销<>
- 与多个 for 循环与单个 for 循环 wrt 相关的性能从多映射获取数据
- 基于范围的 for 循环range_declaration中各种说明符之间的性能差异
- std::p mr::memory_resource 如何与 std::container 产生性能差异?
- 不同的类或结构初始化方法之间的性能差异是什么?