【问题标题】:C++ Making sure 2D vector is compact in memoryC++ 确保 2D 向量在内存中是紧凑的
【发布时间】:2020-11-05 05:16:08
【问题描述】:

我正在编写一个 C++ 程序来在一个巨大的图形上执行计算,因此必须尽可能快。我有一个 100MB 的未加权边文本文件,并将它们读入整数的二维向量(第一个索引 = nodeID,然后是具有该节点边缘的节点的 nodeID 的排序列表)。此外,在程序中,边将按照它们在列表中的存储顺序进行查找。所以我的期望是,除了一些更大的差距之外,它总是可以很好地预加载到缓存中。然而,根据我的分析器,遍历玩家的边缘是一个问题。因此我怀疑,二维向量没有紧凑地放在内存中。

如何确保我的 2D 向量尽可能紧凑,并且子向量按应有的顺序排列? (例如,我想从二维向量创建一个“二维数组”,首先是指针数组,然后是列表。)

顺便说一句:如果不清楚:节点可以有不同数量的边,因此没有选择正常的二维数组。有一些边缘很多,但大多数边缘很少。

编辑:

我已经解决了这个问题,我的程序现在的速度是原来的两倍多: 有第一个解决方案,然后略有改进:

  1. 我将邻居 id 列表放入一维整数数组中,并有另一个数组知道某个 id 的邻居列表从哪里开始

  2. 通过将指针数组(指针需要 64 位)替换为包含索引的 32 位整数数组,我得到了明显的加速

【问题讨论】:

  • 要使二维向量或数组紧凑,请将其声明为 (width * height) 元素的一维数组。您始终可以根据行和列坐标计算一维数组的索引。更多的压缩需要压缩算法。
  • 优化的另一个想法是使用结构或类的数组而不是二维数组。结构确保项目在缓存中彼此相邻;否则处理器可能不得不重新加载缓存以获取另一列中的值。
  • 您可能会发现 Mike Acton 关于data-oriented design 的讨论很有用,尤其是在 36 分钟左右。它不能直接解决您的问题,但它可能会为您提供一些关于如何确保您的数据是缓存友好的想法。这次谈话无疑改变了我对在代码中传递和使用数据的看法。
  • @ThomasMatthews 我为我的国际象棋引擎使用了伪二维数组技巧,但不幸的是我无法计算索引,因为我必须知道之前有多少条目。还是您的意思是将子列表彼此相邻存储在一个数组中,并让另一个数组指向一个玩家开始的位置?我猜那太棒了!
  • @ThomasMatthews 我不明白结构的想法。所以一个结构数组和每个结构内的子列表??

标签: c++ arrays caching vector memory-management


【解决方案1】:

您对二维向量使用什么数据结构?如果你使用 std::vector 那么内存将是连续的。

接下来,如果存储了指针,那么只有地址会利用向量空间局部性。您是否在访问迭代边缘时指向的对象,如果是这样,这可能是一个瓶颈。为了解决这个问题,也许您可​​以设置您的对象,使它们也在连续的内存中并利用空间局部性。

最后,访问向量成员的方式会影响缓存。确保您以有利于使用的容器的顺序访问(例如,在迭代时首先更改列索引)。

这里有一些有用的链接:

Cache Blocking Techniques

SO on cache friendly code

【讨论】:

  • 我已经在从低索引到高索引访问主向量。问题在于它指向的所有子向量都在内存中的某个地方。我想我会用一个指针数组指向另一个包含所有子列表的一维数组(我仍然需要恒定时间访问)
【解决方案2】:

我已经通过将2D 视图放在1D 向量上来编写其中的一些类型结构,并且有很多不同的方法可以做到这一点。我以前从来没有做过一个允许内部数组长度变化的数组,所以这可能包含错误,但应该说明一般方法:

#include <cassert>
#include <iostream>
#include <vector>

template<typename T>
class array_of_arrays
{
public:
    array_of_arrays() {}

    template<typename Iter>
    void push_back(Iter beg, Iter end)
    {
        m_idx.push_back(m_vec.size());
        m_vec.insert(std::end(m_vec), beg, end);
    }

    T* operator[](std::size_t row) { assert(row < rows()); return &m_vec[m_idx[row]]; }
    T const* operator[](std::size_t row) const { assert(row < rows()); return &m_vec[m_idx[row]]; }

    std::size_t rows() const { return m_idx.size(); }

    std::size_t cols(std::size_t row) const
    {
        assert(row <= m_idx.size());
        auto b = m_idx[row];
        auto e = row + 1 >= m_idx.size() ? m_vec.size() : m_idx[row + 1];
        return std::size_t(e - b);
    }

private:
    std::vector<T> m_vec;
    std::vector<std::size_t> m_idx;
};

int main()
{
    array_of_arrays<int> aoa;

    auto data = {2, 4, 3, 5, 7, 2, 8, 1, 3, 6, 1};

    aoa.push_back(std::begin(data), std::begin(data) + 3);
    aoa.push_back(std::begin(data) + 3, std::begin(data) + 8);

    for(auto row = 0UL; row < aoa.rows(); ++row)
    {
        for(auto col = 0UL; col < aoa.cols(row); ++col)
        {
            std::cout << aoa[row][col] << ' ';
        }
        std::cout << '\n';
    }

}

输出:

2 4 3 
5 7 2 8 1 

【讨论】:

    猜你喜欢
    • 2012-04-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多