我一直致力于状态空间探索,并且最初使用地图来存储世界状态的分配,如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
,而是用from
替换to
和id
一些随机数,并且搜索不会做到应该做的事情并且返回得更快那应该。当我调整地图版本以将地图转换为矢量并运行时:
Node n(i, transition.value, label_cost); open_list.push(n);
性能大约等于向量的性能。这样就解决了我的主要问题,但是这让我想知道为什么使用Node *n
会导致这种行为与Node n()
相反?
答案 0 :(得分:0)
问题在于您在不预留空间的情况下创建向量。向量连续存储元素。这确保了对元素的持续访问。
因此,每次向向量添加项目时(例如通过插入器),向量都必须重新分配更多空间,并最终将所有现有元素移动到重新分配的内存位置。这会导致速度减慢和相当大的堆碎片。
如果您事先知道您将拥有多少元素,那么解决方案就是reserve()
元素。或者,如果您不保留()较大的块并比较size()
和capacity()
以检查是否有时间保留更多。
答案 1 :(得分:0)
如果如你所说,这些结构的大小相当小(约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;
}
地图版本需要大约15秒才能在我的旧Core 2 Duo T9600上运行,矢量版需要0.406秒。我们都使用g++ -O3 --std=c++1y
在G ++ 4.9.2上编译。因此,如果你的代码每次迭代需要0.4秒(注意我的示例代码需要0.4s才能进行100万次调用),那么我真的认为你的问题在其他地方。
这并不是说你没有因为从map-&gt;向量切换而导致性能下降,而是你发布的代码没有太多理由发生这种情况。