矢量性能受苦

Vector performance suffering

本文关键字:性能      更新时间:2023-10-16

我一直在研究状态空间探索,最初使用映射来存储世界状态的赋值,如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替换fromto,并且搜索不会做它应该做的事情,并且返回的速度比它应该快得多。当我调整地图版本以将地图转换为矢量并运行以下命令时:

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()以检查是否该保留更多块。