选择性能最佳的容器(阵列)

时间:2010-08-31 08:15:27

标签: c++

这是关于容器的一个小问题,特别是数组。

我正在编写一个物理代码,主要操作一个大的(> 1 000 000)一组“粒子”(每个都有6个double坐标)。我正在寻找最好的方法(在性能方面)来实现一个包含这些数据容器的类,它将为这些数据提供操作原语(例如实例化,operator[]等)。

对此方法的使用方式有一些限制:

  • 其大小从配置文件中读取,在执行期间不会更改
  • 它可以被视为N(例如1 000 000)行和6列(每个存储一维坐标)的大二维数组。
  • 在一个大循环中操作数组,访问每个“粒子/线”并使用其坐标进行计算,并将结果存储回该粒子,依此类推每个粒子,依此类推迭代的大循环。
  • 执行期间未添加或删除任何新元素

第一个结论,因为对元素的访问基本上是通过使用[]逐个访问每个元素来完成的,我认为我应该使用普通的动态数组。

我已经探讨了一些事情,我希望对能给我最佳表现的人有所了解。

据我所知,使用动态分配的数组而不是std::vector没有任何优势,因此排除double** array2d = new ..., loop of new, etc之类的内容。

使用std::vector<double>

是个好主意

如果我使用std::vector,我应该创建像std::vector<std::vector<double> > my_array这样的二维数组,可以像my_array[i][j]一样编入索引,或者这是一个坏主意,最好使用std::vector<double> other_array并使用other_array[6*i+j]访问它。

也许这可以提供更好的性能,特别是当列的数量是固定的并且从一开始就知道。

如果您认为这是最佳选择,是否可以将此向量包装为可以使用定义为other_array[i,j] // same as other_array[6*i+j]的索引运算符进行访问而无需开销(如每次访问时的函数调用) ?

另一种选择,我目前使用的是使用Blitz,特别是blitz::Array

typedef blitz::Array<double,TWO_DIMENSIONS> store_t;
store_t my_store;

我的元素的访问方式如下:my_store(line, column);

我认为在我的情况下使用Blitz没有太大的优势,因为我逐个访问每个元素,如果我直接在数组上使用操作(如矩阵乘法),那么Blitz会很有趣,我不是。 / p>

你认为闪电战是好的,还是在我的情况下没用?

这些是我到目前为止所考虑的可能性,但也许是最好的一种我还是另一种,所以不要犹豫,建议我做其他事情。

非常感谢你对这个问题的帮助!

修改

从非常有趣的答案和评论下面看来,一个好的解决方案如下:

  • 使用结构particle(包含6个双打)或6个双打的静态数组(这样可以避免使用二维动态数组)
  • 使用此vector结构或数组的dequeparticle。然后用迭代器遍历它们是很好的,这将允许稍后从一个变为另一个。

此外,我还可以使用Blitz::TinyVector<double,6>而不是结构。

5 个答案:

答案 0 :(得分:8)

  

使用std::vector<double>

是个好主意

通常,std::vector应该是容器的首选。您可以使用std::vector<>::reserve()std::vector<>::resize()来避免在填充向量时重新分配。是否可以通过 测量 找到更好的其他容器。而且只能通过测量。但首先要衡量容器是否涉及(填充,访问元素)是否值得优化。

  

如果我使用std :: vector,我应该创建像std::vector<std::vector<double> > [...]这样的二维数组吗?

没有。 IIUC,您每个粒子访问您的数据,而不是每行。如果是这种情况,为什么不使用std::vector<particle>,其中particle是一个包含六个值的结构?即使我理解不正确,你也应该在一维容器周围写一个二维包装器。然后按行或列对齐数据 - 访问模式的速度更快。

  

你认为闪电战是好的,还是在我的情况下没用?

我没有关于blitz ++及其所用区域的实用知识。但是,不是blitz ++所有关于表达模板来展开循环操作并在进行矩阵操作时优化临时工作? ICBWT。

答案 1 :(得分:4)

首先,你不想在一个地方散布一个给定粒子的坐标,所以我首先写一个简单的struct

struct Particle { /* coords */ };

然后我们可以制作这些Particles的简单一维数组。

我可能会使用deque,因为这是默认容器,但您可能希望尝试vector,只需要1.000.000的粒子就意味着几个MB的单个块。它应该成立,但如果这种情况有所增长,它可能会使你的系统变得紧张,而deque会分配几个块。

警告

正如Alexandre C所说,如果你走deque路,不要使用operator[]并且更喜欢使用迭代风格。如果您确实需要随机访问且性能敏感,vector应该更快。

答案 2 :(得分:2)

从容器中选择时的第一条规则是使用std::vector。然后,只有在您的代码完成并且您可以实际测量性能之后,您才可以尝试其他容器。但首先坚持矢量。 (并从一开始就使用reserve()

然后,您不应该使用std::vector<std::vector<double> >。你知道数据的大小:它是6倍。不需要它是动态的。它是固定不变的。你可以定义一个结构来保存粒子成员(六个双打),或者你可以简单地输入它:typedef double particle[6]。然后,使用粒子矢量:std::vector<particle>

此外,由于程序按顺序使用向量中包含的粒子数据,因此您将利用现代CPU缓存预读功能获得最佳性能。

答案 3 :(得分:1)

你可以走几条路。但在您的情况下,声明std::vector<std::vector<double> >。你为每6个双打分配一个vector(你复制它)。这太昂贵了。

答案 4 :(得分:1)

  

如果您认为这是最佳选择,是否可以使用定义为other_array [i,j] //与other_array [6 * i相同]的索引运算符来访问此向量+ j]没有开销(每次访问时都有函数调用)?

other_array[i,j]将无法正常工作,因为i,j使用逗号运算符来计算“i”的值,然后丢弃并评估并返回“j”,因此它等同于{{ 1}})。

您需要使用以下其中一项:

other_array[i]

如果你发现它们有用,你可能会或者可能不会在最后两个函数上放置get_和set_或类似的东西,但是从我的问题我认为你不会:函数在大型系统的部分涉及许多的API之间是首选的开发人员,或者当数据项可能不同时,您希望处理数据的算法独立于此。

所以,这是一个很好的测试:如果你发现自己编写的代码如other_array[i][j] other_array(i, j) // if other_array implements operator()(int, int), // but std::vector<> et al don't. other_array[i].identifier // identifier is a member variable other_array[i].identifier() // member function getting value other_array[i].identifier(double) // member function setting value 那么你已经决定“3”是速度加倍的{和} other_array[i][3]因为“5”是加速,然后停止这样做,并给他们正确的标识符,以便你可以说other_array[i][5]other_array[i].speed。然后其他开发人员可以阅读并理解它,并且你不太可能犯下意外错误。另一方面,如果你在迭代那些对每个元素执行完全相同的事情的6个元素,那么你可能希望粒子持有双精度[6],或者提供.acceleration。两者都没有问题:

operator[](int)
BTW / struct Particle { double x[6]; double& speed() { return x[3]; } double speed() const { return x[3]; } double& acceleration() { return x[5]; } ... }; 可能成本太高的原因是每组6个双打将在堆上分配,并且为了快速分配和释放,许多堆实现使用固定大小的桶,因此您的小请求将被舍入到下一个大小:这可能是一个重大的开销。外部向量还需要记录指向该内存的额外指针。此外,堆分配和释放相对较慢 - 在您遇到这种情况时,您只能在启动和关闭时执行此操作,但没有特别的要点让您的程序无缘无故地变慢。更重要的是,堆上的区域可能只在内存中,因此您的operator []可能会有缓存错误拉入更多不同的内存页面,从而减慢整个程序的速度。换句话说,向量连续存储元素,但指向向量可能不是连续的。