为什么haskell的矩阵乘法会花费这么多内存?

时间:2015-11-15 11:06:10

标签: haskell matrix

我需要乘以[2 ^ 14 x 2 ^ 14]个矩阵。但首先,让我们谈谈[2 ^ 12 x 2 ^ 12]矩阵。我需要多少内存?假设它是双矩阵,所以我需要8个字节用于一个元素。

((2 ^ 12 * 2 ^ 12 * 8)/ 2 ^ 20)* 3 = 384 MiB

这是最糟糕的情况,当我需要将所有三个矩阵存储在内存中时。

haskell需要多少内存?我们来看看。

-> let m n = matrix (2^n) (2^n) ( \(i, j) -> (fromIntegral i) * (fromIntegral j) ) :: Matrix Double
-> let p n = let r = m n in multStd2 r r ! (n,n)
-> p 12
3.299742941184e12
(4.84 secs, 1,991,404,304 bytes)

这是~2 GiB。为什么这么糟糕,除了使用C ++我该怎么办?

UPD:

我正在使用标准的haskell的Data.Matrix模块。

https://hackage.haskell.org/package/matrix

2 个答案:

答案 0 :(得分:5)

两点。

  • 您正在寻找“未装箱”的数组(或矢量或矩阵或其他),而非“盒装”数组。未装箱的阵列具有与C相同的平面表示,而装箱阵列是指向存储在阵列外部的堆对象的指针数组。作为额外空间使用的交换,盒装数组可以存储任何类型的元素,还可以存储未评估的值,使其内容不严格。对于与机器字相同大小的类型(假设为64位系统),如Double,盒装数组通常使用的空间是未装箱数组的三倍。

  • 数字“1,991,404,304字节”并不代表您认为它意味着什么。它实际上是在计算期间分配的总内存量。这并没有说明任何时候使用的最大空间量(除了它小于2GB)。对于峰值空间使用值,请使用+RTS -s运行,并在程序完成时查看输出中的“最大驻留时间”值。

答案 1 :(得分:3)

  

为什么这么糟糕,除了使用C ++,我该怎么做。

  1. 确保您使用-O2。在我的测试中,它减少了最大内存 居住地从1095M到544M。运行时间也从3.48秒减少到2.65秒。

  2. 尝试使用更高效的库,如评论中所述。尝试 例如hmatrix

  3. 现在,至于“为什么”。以下是Matrixmatrix类型的实现方式 库:

    data Matrix a = M {
       nrows     :: {-# UNPACK #-} !Int -- ^ Number of rows.
     , ncols     :: {-# UNPACK #-} !Int -- ^ Number of columns.
     , rowOffset :: {-# UNPACK #-} !Int
     , colOffset :: {-# UNPACK #-} !Int
     , vcols     :: {-# UNPACK #-} !Int -- ^ Number of columns of the matrix without offset
     , mvect     :: V.Vector a          -- ^ Content of the matrix as a plain vector.
       }
    

    (假设我们使用的是64位系统)

    我们有5个解压缩的整数,这使得5 x 8 bytes = 40 bytes。为了 实际的矢量数据我们有一个Vector。这不是unboxed vector, 这意味着向量的每个元素都是指向分配的堆的指针 宾语。在我们的例子中,我们有这些功能:

    m :: Int -> Matrix Double
    m n =
      matrix (2^n) (2^n) ( \(i, j) -> (fromIntegral i) * (fromIntegral j) )
    
    p :: Int -> Double
    p n = let r = m n in multStd2 r r ! (n,n)
    

    (添加类型以使其清楚)

    因此我们的Matrix Double大小为2^12 * 2^12 = 16777216双倍。

    现在估计将多少内存存储在内存中 堆,让我们看一下GHC的堆对象表示。

    堆对象表示为:

    typedef struct StgClosure_ {
        StgHeader   header;
        struct StgClosure_ *payload[FLEXIBLE_ARRAY];
    } *StgClosurePtr;
    
    typedef struct {
        const StgInfoTable* info;
    } StgHeader;
    
    typedef struct StgInfoTable_ {
        StgClosureInfo  layout;     /* closure layout info (one word) */
        StgHalfWord     type;       /* closure type */
        StgHalfWord     srt_bitmap;
        StgCode         code[FLEXIBLE_ARRAY];
    } *StgInfoTablePtr;
    
    typedef union {
        struct {                    /* Heap closure payload layout: */
            StgHalfWord ptrs;       /* number of pointers */
            StgHalfWord nptrs;      /* number of non-pointers */
        } payload;
    
        StgWord bitmap;               /* word-sized bit pattern describing */
                                      /*  a stack frame: see below */
        OFFSET_FIELD(large_bitmap_offset);  /* offset from info table to large bitmap structure */
        StgWord selector_offset;      /* used in THUNK_SELECTORs */
    
    } StgClosureInfo;
    

    我们这里有很多簿记。为了简单起见,我们假设 matrix库足够有效,可以避免在这里发生thunking(即matrix 函数是严格的返回值)。在这种情况下,我们可以说我们的 payload只会有实际的双倍。所以这是数学:

    16777216双打。 - 16777216 struct StgClosure_ *表示Double type = 16777216 * 8字节。    - 我们的有效载荷只有双打= 16777216 * 8字节。

    • 16777216双重类型的StgHeader。
      • 16777216 StgInfoTable* = 16777216 * 8字节。
      • A StgInfoTable包括:
        • 1 StgClosureInfo =至少3个字= 24个字节。
        • 2个半字= 8个字节。
        • 一个字节数组。在我们的例子中,我认为这将是0字节。所以只是一个 指针= 8个字节。

    当我们将它们相加时,它会产生1073M。

    现在,这场比赛非常波动,但希望每一堆都清楚 对象那里有很多记账。更确切地说,我们需要 采取像共享信息表这样的东西(例如,据我所知,相同的闭包会 我认为有相同的信息表?)。