【问题标题】:Is there an efficient way to slice a C++ vector given a vector containing the indexes to be sliced给定包含要切片的索引的向量,是否有一种有效的方法来切片 C++ 向量
【发布时间】:2021-05-06 22:04:53
【问题描述】:

我正在努力实现一个用 MATLAB 编写成 C++ 的代码。

在 MATLAB 中,您可以将一个数组与另一个数组(例如 A(B))进行切片,从而在 B 中元素的值指定的索引处生成一个 A 元素的新数组。

我想在 C++ 中使用向量做类似的事情。这些向量的大小为 10000-40000 个 double 类型的元素。

我希望能够使用另一个包含要切片的索引的 int 类型向量来切片这些向量。

例如,我有一个向量 v = 和一个向量 w = 。我想使用 w 对 v 进行切片,使得切片的结果是一个新向量(因为旧向量必须保持不变) x = .

我想出了一个函数来做到这一点:

template<typename T>
std::vector<T> slice(std::vector<T>& v, std::vector<int>& id) {

    std::vector<T> tmp;
    tmp.reserve(id.size());

    for (auto& i : id) {
        tmp.emplace_back(v[i]);
    }

    return tmp;
}

我想知道是否有更有效的方法来完成这样的任务。速度是这里的关键,因为这个切片函数将处于一个具有大约 300000 次迭代的 for 循环中。我听说 boost 库可能包含一些有效的解决方案,但我还没有使用它的经验。

我使用 chrono 库来测量调用此切片函数所需的时间,其中要切片的向量长度为​​ 37520,包含索引的向量大小为 1550。对于此函数的单次调用,经过的时间= 0.0004284 秒。然而,超过约 300000 次 for 循环迭代,总运行时间为 134 秒。

任何建议都会非常感激!

【问题讨论】:

  • 返回 tmp 向量是 seg 错误吗?我原以为您必须通过 ref 传递或使用动态存储(可能也包含在智能 ptr 中)。
  • for (auto i : id) 应该比for (auto&amp; i : id) 运行得更快,因为它在最内层循环的每次迭代中减少了一次解引用。此外,将您的函数参数声明为 const ref 可能有助于编译器优化您的代码。
  • @jackw11111 不,按值返回临时或局部变量是可以的。不是通过引用返回一个。
  • @PaulSanders 是因为 tmp 向量中的值比函数长吗? IE。 id 正在由 ref 传递?
  • @jackw11111 我不太清楚你所说的段错误是什么意思

标签: c++ vector boost slice


【解决方案1】:

emplace_back 有一些开销,因为它涉及std::vector 内部的一些内部会计。试试这个:

template<typename T>
std::vector<T> slice(const std::vector<T>& v, const std::vector<int>& id) {

    std::vector<T> tmp;
    tmp.resize (id.size ());

    size_t n = 0;
    for (auto i : id) {
        tmp [n++] = v [i];
    }

    return tmp;
}

另外,我在您的内部循环中删除了不必要的取消引用。


编辑:我对此进行了更多思考,并受到@jack 回答的启发,我认为内部循环(这是最重要的)可以进一步优化。这个想法是将循环使用的所有内容都放在局部变量中,这为编译器提供了优化代码的最佳机会。所以试试这个,看看你得到了什么时间。确保您测试发布/优化的构建:

template<typename T>
std::vector<T> slice(const std::vector<T>& v, const std::vector<int>& id) {

    size_t id_size = id.size ();
    std::vector<T> tmp (id_size);
    T *tmp_data = tmp.data ();

    const int *id_data = id.data ();
    const T* v_data = v.data ();

    for (size_t i = 0; i < id_size; ++i) {
        tmp_data [i] = v_data [id_data [i]];
    }

    return tmp;
}

【讨论】:

  • 我将旧函数的 300000 次迭代与您建议的进行了比较,您的执行速度快了大约 5 倍!所以从~123s 到 24s 的改进。为此非常感谢。在我给它打勾之前,我会等着看是否有其他答案,但这有很大帮助
  • 嗯,只是展示,你永远看不出来。
  • 我一直以为emplace_back 比assignment 快,但是这说明我很不正确
  • std::vector&lt;T&gt; tmp(id.size());
  • @luca 默认构造一个空向量然后分配内存与在构造时进行分配之间的区别可能并不显着。我确实认为构建具有所需大小的向量更清楚地表达了您的意图(并且打字更少)。
【解决方案2】:

性能似乎有点慢;您是否使用编译器优化进行构建(例如g++ main.cpp -O3 或者如果使用 IDE,则切换到发布模式)。仅此一项就将计算时间加快了大约 10 倍。

如果您已经在使用优化,通过使用基本的 for 循环迭代 (for int i = 0; i &lt; id.size(); i++) 计算时间在我的机器上加快了大约 2-3 倍,这个想法是,编译器不必解析 auto 指的是什么类型到,而且由于基本的 for 循环一直在 C++ 中,编译器可能有很多技巧来加速它。

template<typename T>
std::vector<T> slice(const std::vector<T>& v, const std::vector<int>& id){

    // @Jan Schultke's suggestion
    std::vector<T> tmp(id.size ());

    size_t n = 0;
    for (int i = 0; i < id.size(); i++) {
        tmp [n++] = v [i];
    }

    return tmp;
}

【讨论】:

  • 这对性能有任何影响真的很奇怪。 auto 与它无关,因为 auto 是在编译时静态推断的。从表面上看,GCC 和 clang 无法向量化基于范围的代码,可能是因为基于范围的循环依赖于指针算法。 godbolt.org/z/T3cY8PP83 使用 MSVC,应该没有任何区别,因为它也不需要矢量化。
  • 哦,是的,它是在编译时解决的。嗯,我不确定如何处理这个误报。我测试了基于范围的 for 循环是否比自动 for 循环更快,因为“简单胜于复杂”,并且它有效,因此我尝试推断结果(根据我对编译器的有限知识)。
  • 您(和 OP)是否在启用优化的情况下进行编译?
  • @PaulSanders 是的,不确定 OP。
  • @luca 我对visual studio不太熟悉,但是当你从调试模式切换到发布模式时,它应该是内置的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-08-12
  • 1970-01-01
  • 2020-09-27
  • 2014-06-10
  • 2018-07-10
  • 2017-07-29
  • 1970-01-01
相关资源
最近更新 更多