有没有一种有效的方法来切片 C++ 向量,给定一个包含要切片的索引的向量

Is there an efficient way to slice a C++ vector given a vector containing the indexes to be sliced

提问人:luca 提问时间:5/7/2021 更新时间:11/17/2023 访问量:2242

问:

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

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

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

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

例如,我有一个向量 v = <1.0、3.0、5.0、2.0、8.0>和一个向量 w = <0、3、2>。我想使用 w 对 v 进行切片,使切片的结果是一个新向量(因为旧向量必须保持不变)x = <1.0、2.0、5.0>。

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

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.0004284s。但是,在 ~300000 次 for 循环迭代中,总运行时间为 134 秒。

任何建议都会非常感激!

C++ 矢量 提升 切片

评论

0赞 jackw11111 5/7/2021
返回 tmp 向量是 seg 错误吗?我本来以为你必须通过 ref 或使用动态存储(也许也包装在智能 ptr 中)。
1赞 Paul Sanders 5/7/2021
for (auto i : id)应该比 运行得更快,因为它涉及的最内层循环的每次迭代少一个取消引用。此外,将函数参数声明为 const ref 可能有助于编译器优化代码。for (auto& i : id)
1赞 Paul Sanders 5/7/2021
@jackw11111 不可以,按值返回临时变量或局部变量是可以的。通过引用返回一个不是。
0赞 jackw11111 5/7/2021
@PaulSanders 这是因为 tmp 向量中的值比函数长寿吗?即。id 正在被 ref 传递?
0赞 luca 5/7/2021
@jackw11111 我不太确定你说的赛格故障是什么意思

答:

3赞 Paul Sanders 5/7/2021 #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;
}

评论

1赞 luca 5/7/2021
我将我的旧函数的 300000 次迭代与您建议的函数进行了比较,您的执行速度提高了大约 5 倍!因此,从 ~123 秒提高到 24 秒。非常感谢。在我打勾之前,我会等着看是否有其他答案,但这有很大帮助
0赞 Paul Sanders 5/7/2021
嗯,只是去展示,你永远说不出来。
0赞 luca 5/7/2021
我一直认为emplace_back比作业快,但这表明我对此非常不正确
3赞 Jan Schultke 5/7/2021
std::vector<T> tmp(id.size());
1赞 Blastfurnace 5/7/2021
@luca 默认构造空向量然后分配内存与在构造时进行分配之间的区别可能并不显著。我确实认为构建具有所需大小的向量可以更清楚地表达您的意图(并且更少键入)。
3赞 7 revsjackw11111 #2

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

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

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;
}

评论

1赞 Jan Schultke 5/7/2021
这确实很奇怪,这根本没有任何性能影响。 与它无关,因为它是在编译时静态推断的。从外观上看,GCC 和 clang 无法对基于范围的代码进行矢量化,这可能是因为基于范围的循环依赖于指针算术。godbolt.org/z/T3cY8PP83使用 MSVC,应该没有任何区别,因为它也不会打扰矢量化。autoauto
0赞 jackw11111 5/7/2021
哦,没错,它是在编译时解决的。嗯,我不确定该如何看待这个误报。我测试了基于范围的 for 循环是否比自动 for 循环更快,因为“简单比复杂好”,并且它有效,所以我试图推断结果(根据我对编译器的有限了解)。
0赞 Paul Sanders 5/8/2021
您(和 OP)是否在启用优化的情况下进行编译?
0赞 jackw11111 5/8/2021
@PaulSanders 是的,不确定 OP。
1赞 jackw11111 5/8/2021
@luca我对 Visual Studio 不太熟悉,但当您从调试模式切换到发布模式时,它应该是内置的。
0赞 RAM 11/17/2023 #3

我想使用向量在C++中做类似的事情。

C++ 对 Matlab 可切片向量的回答不是;它。std::vectorstd::valarray

https://en.cppreference.com/w/cpp/numeric/valarray

C++ 支持借助 实例化其他对象。std::valarraystd::valarraystd::slice

一旦你有了你的valarray切片对象,C++显式地支持将标量运算应用于它的每个元素,不仅使用基本的算术运算,而且还通过将函数传递给每个元素来应用函数。std::valarraystd::valarray::apply