【问题标题】:Vector performance suffering矢量性能受苦
【发布时间】:2014-12-08 19:17:53
【问题描述】:

我一直致力于状态空间探索,最初使用地图来存储世界状态的分配,例如 map<Variable *, int>,其中变量是世界中的对象,其域从 0 到 n,其中 n 是有限的。该实现的性能非常快,但我注意到它不能很好地随着状态空间的大小而扩展。我将状态更改为使用 vector<int> 代替,我使用变量的 id 在向量中查找其索引。内存使用率大大提高,但求解器的效率已经下降(从

原来我是这样生成节点的:

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 容器我可以使用它具有较低的开销?变量的数量相对较少(

编辑:

我尝试通过所有运算符计时一个循环以查看时间比较,效果列表和分配矢量版本在 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,而是将fromto 替换为带有一些随机数的id,并且该搜索没有执行应有的操作,并且返回的速度比应有的快得多。当我调整地图版本以将地图转换为矢量并运行时:

Node n(i, transition.value, label_cost); open_list.push(n); 性能大约等于向量的性能。这样就解决了我的主要问题,但这让我想知道为什么使用 Node *n 会得到与 Node n() 相反的这种行为?

【问题讨论】:

  • 你也尝试过 unordered_map 吗?您的向量是否已排序?如果您确保始终对向量进行排序,您可以更快地完成操作。
  • 您是否有任何其他信息说明导致减速的原因?总是使用g++ -pggprof 进行编译。否则,我假设get_id() 的计算成本不高,eff_it-&gt;var-&gt;get_id() 生成的 id 是顺序的还是随机的?
  • 我不希望向量排序为值的索引对应于特定变量,即 var0 位于索引 0 等。ID 是在解析输入时生成的,因此在加载 var0 时变量有一个私有 int,这就是返回的内容。
  • var_assignment 和 assignment 是否有相同数量的元素?您的assignment 向量中是否有任何未使用的id
  • 是的,两者都具有相同数量的元素,并且assignment 向量的大小与元素数量相同,并且永远不会改变。所有状态的矢量/地图大小相同。

标签: c++ performance vector


【解决方案1】:

如果如您所说,这些结构的大小相当小(约 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;
}

在我的旧 Core 2 Duo T9600 上运行地图版本大约需要 15 秒,而矢量版本需要 0.406 秒。我们都使用g++ -O3 --std=c++1y 在 G++ 4.9.2 上编译。因此,如果您的代码每次迭代耗时 0.4 秒(请注意,我的示例代码在 100 万次调用中耗时 0.4 秒),那么我真的认为您的问题出在其他地方。

这并不是说您没有因为从 map->vector 切换而导致性能下降,而是您发布的代码并没有说明发生这种情况的太多理由。

【讨论】:

    【解决方案2】:

    问题是您创建向量时没有保留空间。向量连续存储元素。这确保了对元素的持续访问。

    因此,每次您向向量添加项目时(例如通过插入器),向量都必须重新分配更多空间并最终将所有现有元素移动到重新分配的内存位置。这会导致速度变慢和相当大的堆碎片。

    如果您事先知道您将拥有多少个元素,则解决方案是reserve() 元素。或者,如果您不保留()更大的块并比较 size()capacity() 以检查是否是时候保留更多。

    【讨论】:

    • 我看不出这与地图示例有何不同?另外我只看到一个副本分配,然后是更多的分配,没有插入。
    • 我认为不能保证地图上的连续性。这意味着如果您添加一个新元素,该元素会被分配到某个地方,并且地图会引用它。您可以通过打印出两种情况下元素的地址来检查。
    • 是的,但最终需要分配相同数量的内存(由于键/结构,在映射的情况下更多,假设向量中没有空点)。因此,如果有的话,我预计地图会更糟。
    • 另一件事,我从不插入向量中。 child = assignment 时生成了一份副本,循环中的所有内容都只是访问向量中的索引。因此,如果向量有 10 个元素,则最大 id 为 9,并且在程序生命周期内不会更改该大小。
    • @user63710 不,因为在地图中,您只需为一个元素分配内存,而对于向量,您必须为所有元素(新元素 + 所有其他元素)分配内存并移动所有元素元素到新位置(调用移动或复制构造函数)。如果你有几百万个元素,就像我两周前的基准测试一样,它确实会产生影响,来回移动 GB 的内存。
    猜你喜欢
    • 2021-03-13
    • 2018-06-11
    • 2014-08-23
    • 2021-01-17
    • 2023-04-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-05
    • 1970-01-01
    相关资源
    最近更新 更多