【问题标题】:std::vector vs normal arraystd::vector 与普通数组
【发布时间】:2012-04-18 06:47:57
【问题描述】:

我正在创建一个需要超快的程序。 它使用 CUDA 在 GPU 上运行一些东西,然后在 CPU 上进行一些计算。为此,我需要将高度优化的 GPU 数据结构转换为我可以在 CPU 上轻松使用的东西。我的数据基本上是一个以网格布局的图表。 目前我正在使用 std::vector 作为 CPU 部分。因为我知道如果我做很多 push_back()s 会有相当大的开销,而且我至少知道因为我知道我的图中有多少个顶点,所以我现在为此使用以下代码:

new_graph.resize(blockSize * blockSize);
for (unsigned long long y = 0; y < blockSize; y++) {
    for (unsigned long long x = 0; x < blockSize; x++) {
        int idx = y * blockSize + x;
        new_graph[idx] = Vertex(x, y);
    }
}

然后我添加边缘。不幸的是,我不知道每个顶点有多少条边,但我知道它永远不会大于 8。因此我在每个用于边的 std::vector 中 reserve() 8。

但是,这两者似乎都非常缓慢。如果我对图形本身使用普通数组(所以基本上替换了外部 std::vector),那部分的速度提升是巨大的(比如 10 倍左右)。

对于图形这是可行的,但对于边缘不是真的,因为我在这些边缘上做了一些后处​​理,为此我真的需要像 std::vector 这样有点动态的东西(我添加了一些边缘)。

目前将数据转换为 std::vector 的速度比在 GPU 上运行我的算法(这是一种智能 MST 算法)慢 10 倍。这不是我真正想要的,因为现在开销太大了。

有人知道发生了什么或者我该如何解决这个问题?

附言我使用 -O2 进行编译,因为我已经发现这会产生很大的不同。也尝试了 -O3,没有真正的区别。

顶点定义如下:

struct Pos {
    int x, y;
    Pos() {
        x = 0;
        y = 0;
    }

    Pos(int x, int y) {
        this->x = x;
        this->y = y;
    }
};

struct Vertex {
    Pos pos;
    bool hidden;
    unsigned long long newIdx;
    Vertex() {
        this->pos = Pos();
        this->hidden = false;
        this->numEdges = 0;
        this->numRemovedEdges = 0;
    }

    Vertex(Pos &pos) {
        this->pos = pos;
        this->hidden = false;
        this->numEdges = 0;
        this->numRemovedEdges = 0;
    }

    Vertex(int x, int y) {
        this->pos = Pos(x, y);
        this->hidden = false;
        this->numEdges = 0;
        this->numRemovedEdges = 0;
    }
    int numEdges;
    int numRemovedEdges;
    std::vector<Edge> edges;
    std::vector<bool> removed;
    std::vector<bool> doNotWrite;
};

【问题讨论】:

  • 尝试使用 -O3 编译,这将内联一些函数(99.999% 的机会它会内联 push_back,如果没有,那么实现或编译器就是一坨屎)。跨度>
  • @daknok_t 也试过了,没有真正的区别。
  • 调用reserve而不是resize然后使用push_back而不是[]将避免resize执行的冗余初始化。我不知道这是否是导致 10 倍减速的原因(我怀疑它说明了一切),但它肯定会有所帮助。
  • @R.MartinhoFernandes 我试过了,没用。
  • 试试 gprof,你可以看到实际时间都去哪儿了

标签: c++ arrays performance stl


【解决方案1】:

你不能创建一个 Vertex 对象,将 x 和 y 值 memcpy 到其中(这样你就不必为每个循环调用构造函数),然后将整个 Vertex memcpy 到你的 std::vector 中吗?向量的内存保证像常规数组一样布局,因此您可以绕过所有抽象并直接操作内存。不需要复杂的东西。此外,也许您可​​以对从 GPU 获取的数据进行布局,以便一次 memcpy 整个块,从而节省更多。

【讨论】:

  • 谢谢,我明天试试看:)。
【解决方案2】:

也许您正在为vector 为其元素保留空间而进行的动态内存分配付费?

即使您最优化reserve,您也将至少为每个Vertex 分配3 个内存分配(一个用于edges,一个用于removed,一个用于@ 987654326@)。相对于您在此处尝试执行的高性能工作而言,动态内存分配可能会很昂贵。

要么使用保证足够大的普通旧数组(可能会浪费空间),要么使用专门的内存分配器以及vector,以满足您的特定需求。


另外,您是否按内存顺序访问元素?您的示例似乎表明了这一点,但是您是否在所有情况下都这样做?


另外,你还需要Vertex.pos吗?不能从Vertex 在网格中的位置推断吗?

【讨论】:

  • 我现在正在研究普通的旧数组,认为这会有所作为。我并不总是按顺序访问它们,并且 Vertex.pos 是必要的,因为我后来从我的结构中删除了节点,所以我不能再使用网格的位置了。
  • 最后我决定创建自己的数组,这提高了速度
【解决方案3】:

我最近在类似情况下使用了另一种解决方案。 在 llvm 包中有 SmallVector 类。它提供了与 std::vector 非常相似的接口,但它允许保持一些固定数量的元素内联(因此,除非向量增长超过初始限制,否则不会发生额外的内存分配)。 如果 SmallVector 试图增长到超过该初始大小,则分配内存块,并将所有项目移到那里 - 全部在一个透明步骤中。

我必须在这个 SmallVector 中解决的一些问题:

  1. 可以就地放置的最少项目数是 2,因此当使用 1 个项目时,例如99.99% 的情况有相当的开销
  2. 通常使用 swap() 来释放内存 ( SmallVector().swap(vec) ) 不会释放内存,所以我必须自己实现它

SmallVector 类的源代码只需要查找最新版本的 llvm 即可

【讨论】:

    【解决方案4】:

    由于动态内存分配的数量、不必要的分配操作以及每个 Vertex 的总体大小,CPU 数据结构的效率极低。在考虑优化此结构之前,最好了解 CPU 数据结构和 GPU 数据结构之间的数据流,因为这两种格式之间的转换可能需要很多时间。这就引出了一个问题,为什么CPU端不使用GPU结构?

    如果您只是从 CPU 方面来看这个并且您想维护一个 AoS 数据结构,那么 1. 简化 Vertex 数据结构。 2.删除所有动态内存分配。每个 std::vector 都会做一个 dynb 3. 将removed 和doNotWrite 替换为std::bitset。 4. 删除 numRemoveEdges。这是删除的.count()。 5. 如果 Edge 很小,那么您可能会发现声明 Edge 边[8] 会更快。 6. 如果您决定继续使用向量,请考虑使用池分配器。 7.将Vertex中的数据元素按大小重新排序,减小Vertex的大小。

    所有这些建议很可能都不是与 GPU 共享数据的最佳解决方案。如果您确实使用了池分配器并且使用了 UVA (CUDA Linux),您可以使用单个内存副本简单地将数据复制到 GPU。

    【讨论】:

    • 感谢您的提示,将尝试其中一些。
    猜你喜欢
    • 2021-05-26
    • 2011-09-16
    • 2014-11-29
    • 1970-01-01
    • 1970-01-01
    • 2020-08-20
    • 2020-09-22
    相关资源
    最近更新 更多