用于高性能计算的c++ std::vector

C++ std::vector for HPC?

本文关键字:std vector c++ 高性能 计算 用于      更新时间:2023-10-16

我正在把一个执行数值模拟的程序从FORTRAN翻译成c++。

我必须处理800MB大小的两倍的大矩阵。这个

double M[100][100][100][100];

给出一个分割错误,因为堆栈不是很大。使用new、delete是很尴尬的,因为我需要四个for循环来分配我的数组,甚至释放它。

std::array在堆栈中,所以它不是很好。Std::vector是个不错的选择,所以

第一个问题std::vector适合快速模拟还是

vector<vector<vector<vector<int,100>,100>,100>,100> 

将携带大量无用和沉重的数据?

第二个问题你知道我可以使用的其他数据结构吗?

目前我只是使用这个解决方案:

double * M = new double [100000000];

,我手动访问我需要的条目。如果我找不到任何其他高性能解决方案,我将编写一个类来自动管理最后一个方法。

第三个问题你认为这会显著降低性能吗?

您可能想要考虑设计为与FORTRAN竞争的std::valarray。它将元素存储为平面数组,并支持数学操作,以及切片和间接访问操作。

听起来像是你的计划。尽管手册也暗示可能有更灵活的替代方案。

在堆栈上使用某些东西肯定更高效。进程内存受到操作系统的限制(堆栈+堆),您的问题是,在大多数情况下,您可能会超过分配给进程的内存。

要解决内存限制,我建议您看一下stxxl。它是一个实现大多数STL容器和算法的库,但在需要时使用外部内存。当然这会影响性能…

程序员倾向于先编写更多的代码来解决每个问题。然后需要维护。每个问题都是不是一个钉子…

在这里,更多的代码并不是最简单、最有效的解决方案。更多的代码也可能产生更慢的可执行文件

堆栈内存就是内存——它和堆内存没有什么不同。它只是由不同的流程管理,并且受到不同的资源限制。对于操作系统来说,进程是在栈上使用1gb内存还是从堆中使用1gb内存并没有真正的区别。

在这种情况下,堆栈大小限制可能是人为的配置设置。在Linux系统上,可以为shell及其子进程重置堆栈大小限制:

bash-4.1$ ulimit -s unlimited
bash-4.1$ ulimit -s
unlimited
bash-4.1$

请参阅这个问题及其答案了解更多细节。

所有posix兼容的系统都应该有类似的特性,因为堆栈大小限制是posix标准的资源限制。

同样,您可以很容易地运行具有任意大堆栈的线程:

#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <stdio.h>
void *threadFunc( void *arg )
{
    double array[1024][1024][64];
    memset( array, 0, sizeof( array ) );
    return( NULL );
}
int main( int argc, char **argv )
{
    // create and memset the stack lest the child thread start thrashing
    // the machine with "try to run/page fault/map page" cycles
    // the memset will create the physical page mappings
    size_t stackSize = strtoul( argv[ 1 ] ? argv[ 1 ] : "1073741824",
        NULL, 0 );    
    void *stack = mmap( 0, stackSize, PROT_READ | PROT_WRITE,
        MAP_PRIVATE | MAP_ANON, -1, 0 );
    memset( stack, 0, stackSize );
    // create a large stack for the child thread
    pthread_attr_t attr;    
    pthread_attr_init( &attr );
    pthread_attr_setstacksize( &attr, stackSize );
    pthread_attr_setstackaddr( &attr, stack );
    pthread_t tid;
    pthread_create( &tid, &attr, threadFunc, NULL );
    void *result;
    pthread_join( tid, &result );
    return( 0 );
}

错误检查已省略。

如果在运行编译后的程序之前运行ulimit -s unlimited(当然,如果机器有足够的虚拟内存…):

#include <string.h>
int main( int argc, char **argv )
{
    double array[1024][1024][64];
    memset( array, 0, sizeof( array ) );
    return( 0 );
}

在某些情况下,将1-D指针强制转换为4-D矩形数组以启用笛卡尔索引(而不是线性索引)可能会很有用:

#include <cstdio>
#define For( i, n ) for( int i = 0; i < n; i++ )
double getsum( double *A, int *n, int loop )
{
    // Cast to 4-D array.
    typedef double (* A4d_t)[ n[2] ][ n[1] ][ n[0] ];
    A4d_t A4d = (A4d_t) A;
    // Fill the array with linear indexing.
    int ntot = n[0] * n[1] * n[2] * n[3];
    For( k, ntot ) A[ k ] = 1.0 / (loop + k + 2);
    // Calc weighted sum with Cartesian indexing.
    double s = 0.0;
    For( i3, n[3] )
    For( i2, n[2] )
    For( i1, n[1] )
    For( i0, n[0] )
        s += A4d[ i3 ][ i2 ][ i1 ][ i0 ] * (i0 + i1 + i2 + i3 + 4);
    return s;
}
int main()
{
    int n[ 4 ] = { 100, 100, 100, 100 };
    double *A = new double [ n[0] * n[1] * n[2] * n[3] ];
    double ans = 0.0;
    For( loop, 10 )
    {
        printf( "loop = %dn", loop );
        ans += getsum( A, n, loop );
    }
    printf( "answers = %30.20fn", ans );
    return 0;
}

在Mac OSX 10.9上使用g++-6.0 -O3需要5.7秒。将性能与基于vector<vector<...>>或自定义数组视图类的性能进行比较可能会很有趣。(我之前尝试过后者,当时上面的数组强制转换比我的(幼稚的)数组类要快一些。)

我认为这个问题的答案是基于个人观点的。""的概念严格依赖于数据结构的使用。

无论如何,如果元素的数量在执行期间没有改变,并且您的问题实际上是内存访问,那么在我看来,最好的解决方案是一个连续的块数组。

通常在这些情况下,我的选择是将一个简单的T* data = new T[SIZE];封装到一个正确处理访问的类中。

指针的使用让我对内存控制感觉更舒服,但实际上std::vector<T>实际上是同样的事情。


根据你在问题中提供的知识,我只能说这些。无论如何,我可以额外建议你也要注意数据的使用。

例如:为了最大化您的性能应用程序,尝试利用缓存并避免错过,因此您可以尝试了解是否有一些"访问模式"到您的数据,或者,甚至,如果您可以将问题扩展到多线程上下文中。


综上所述,在我看来,通常double的连续向量是最佳选择。这就回答了你的问题。但是如果您关心性能,您应该考虑如何尽可能地利用缓存处理器机制(如多线程)。