【问题标题】:Can I convert my 1D vector to a 2D vector faster than this?我可以比这更快地将我的 1D 矢量转换为 2D 矢量吗?
【发布时间】:2021-02-14 00:31:39
【问题描述】:

这个问题很简单。经过一些试验,这是我发现的最有效的代码:

//For the sake of the example, I initialize every entry as zero.
vector<float> vector1D(1024 * 768, 0); 
vector<vector<float>> vector2D(768, vector<float>(1024,0));

int counter = 0;
for (int i = 0; i < 768; i++) {
    for (int j = 0; j < 1024; j++) {
        vector2D[i][j] = vector1D[counter++];
    }
}

有没有更快的方法?

【问题讨论】:

  • 一种非常有效的方法是创建一个视图,该视图提供二维向量的接口,同时仍然是一维向量。
  • 您可以尝试std::copy 而不是内部循环for (int j = 0; j &lt; 1024; j++) {,编译器可能能够生成代码,从而更有效地一步复制1024 个元素。但是你确定你真的想要一个vector&lt;vector&lt;float&gt;&gt;吗?通常,您希望处理存储在这样一个矩阵中的数据,然后将这些数据持续存储在内存中,这在大多数情况下会更有效。
  • @MA 注意。我会调查一下,因为我以前从未听说过意见。
  • 如果你真的需要复制数据,我认为你这样做的方式是最好的。如果您想要的是能够像访问二维数组一样方便地访问数据,您可以创建一个重载 operator[] 并返回 std::span 的包装类(如果您使用的是 c++17)或只是一个指针。
  • vector2D[i][j] 类似于vector2D.ptr_to_data[i].ptr_to_data[j],内存不一定连续在内存中。这可能会导致缓存未命中并且比vector1D[j+i*1024] 慢。大多数对矩阵进行大量计算的库都将它们连续存储在内存中。与vector1D[j+i*1024] 相比,vector2D[i][j] 这样的东西看起来更简单,但这并不意味着它更高效。

标签: c++ for-loop vector 2d-vector


【解决方案1】:

是的。

您可以重新映射访问元素的方式,而无需复制它们。您可以创建一个“视图”类来实现这一点:

template<typename T>
class two_dee_view
{
public:
    two_dee_view(std::vector<T>& v, std::size_t row, std::size_t col)
        : v(v), stride(col) { if(v.size() < row * col) v.resize(row * col); }

    T& operator()(std::size_t row, std::size_t col)
        { return v[(row * stride) + col]; }

    T const& operator()(std::size_t row, std::size_t col) const
        { return v[(row * stride) + col]; }

    std::size_t col_size() const { return stride; }
    std::size_t row_size() const { return v.size() / stride; }

private:
    std::vector<T>& v;
    std::size_t stride;
};

int main()
{
    std::vector<double> v {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};

    two_dee_view<double> v2d(v, 2, 3);

    for(auto row = 0U; row < v2d.row_size(); ++row)
        for(auto col = 0U; col < v2d.col_size(); ++col)
            std::cout << row << ", " << col << ": " << v2d(row, col) << '\n';
}

输出:

0, 0: 1
0, 1: 2
0, 2: 3
1, 0: 4
1, 1: 5
1, 2: 6

该类只维护一个引用到您传递给构造函数std::vector。只要原来的std::vector 存在但不再存在,您就应该只使用two_dee_view

【讨论】:

    【解决方案2】:

    使用memcpy 可能会更快,因为这是用于复制内存的 API 的最低级别,并且可能存在可能使用特定指令等的编译器优化,如果更快:

    for (int i = 0; i < 768; i++) {
        memcpy(vector2D[i].data(), &vector1D[i * 1024], sizeof(float) * 1024);
    }
    

    请记住,您不应该将 memcpy 用于任何可复制的数据。也就是说,它适用于floatint,但不适用于类,因为不会调用复制构造函数。

    【讨论】:

    • 哇,我只是在调试模式下检查,但计算时间从大约 50 毫秒下降到 2 毫秒。这样就行了!
    • @JohnKatsantas 比较调试模式下的性能差异并没有真正的帮助。有些东西在调试中可能会更快,但在发布版本中会慢一些。并且通过优化来衡量发布版本的性能是很棘手的,因为您需要避免编译器完全优化事物,因为您不会对结果进行任何操作。
    • @t.niese 我已经编码多年了,但在大学里,我们实际上从未如此详细地优化我们的代码。这是我第一次经历这样的细节。比我预想的还要麻烦。直到昨天,我什至不知道调试模式会减慢我的代码速度。现在你告诉我在发布模式下有些东西可能会更慢。我现在很困惑:P
    • @t.niese 但是,即使在调试模式下,从 50ms 减少到 2ms 也必须是有意义的。我的意思是,由于我在调试模式下尝试了两种方法,因此至少比较这两种方法应该是可以的,即使它们的实际时间(50 和 2 毫秒)可能不正确。对吗?
    • @JohnKatsantas 不,在调试中需要 50 毫秒的东西在发布中可能只需要 0.1 毫秒,而在调试中需要 2 毫秒的东西在发布中可能需要 1 毫秒。根据您在调试版本中所做的测量,您无法对打开优化后哪些产品在发布中表现更好做出任何假设。
    【解决方案3】:

    如果您出于某种原因必须使用向量向量,则使用 memcpymemmove 更快(因为它是一个步骤,如另一个回复中所述)。但是你应该使用 STL 而不是自己做。

    vector<float> vector1D(1024 * 768, 0);
    vector<vector<float>> vector2D(768, vector<float>(1024, 0));
    
    for (int i = 0; i < 768; i++) {
      vector2D[i].assign(next(vector1D.cbegin(), 1024 * i),
                         next(vector1D.cbegin(), 1024 * (i + 1)));
    }
    

    这会产生直接的memmove(取决于 STL 实现),但更安全、优化和(可能)可读。

    【讨论】:

      猜你喜欢
      • 2022-01-22
      • 2018-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-16
      相关资源
      最近更新 更多