std :: vector vs普通数组

时间:2012-04-04 14:26:46

标签: c++ arrays performance stl

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

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上运行我的算法慢10倍(这是一种智能MST算法)。这不是我想要的,因为现在开销太大了。

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

P.S。我用-O2编译,因为我已经发现这可以产生很大的不同。也试过-O3,没有真正的区别。

Vertex定义如下:

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

4 个答案:

答案 0 :(得分:3)

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

即使您reserve达到最佳状态,每个Vertex (一个用于edges,一个用于removed至少有 3个内存分配{1}}和doNotWrite之一。相对于您尝试在此处执行的高性能内容,动态内存分配可能很昂贵。

使用保证足够大的普通旧数组(可能浪费空间),或使用专门的内存分配器和vector,根据您的特定需求进行定制。


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


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

答案 1 :(得分:1)

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

我必须在这个SmallVector中解决几件事:

  1. 可以放置到位的最小数量的项目是2,因此当在例如1中使用1个项目时99.99%的案例存在相当大的开销
  2. 通常使用swap()释放内存(SmallVector(。。swap(vec))不释放内存,所以我必须自己实现
  3. 只需查看最新版本的llvm for SmallVector类的源代码

答案 2 :(得分:1)

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

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

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

答案 3 :(得分:0)

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