【问题标题】:Eigen library memory usage for dynamic vectors动态向量的特征库内存使用情况
【发布时间】:2014-04-08 08:59:26
【问题描述】:

我有一个存储 float32 对象的二进制文件(其中 9748422*5 个)。从这样一个集合(大约 190MB 大小)中,我创建了一组 Eigen::VectorXd 向量(每个向量有 5 个分量),因此其中有 9748422 个。底层类型是double,因此存储它们的输入大小大约是两倍。

但是,幸运的是,该过程总共需要 2.5GB。这是PROCESS_MEMORY_COUNTERS的日志:

    PageFaultCount: 0x000A3C40
    PeakWorkingSetSize: 0xA3C42000
    WorkingSetSize: 0xA3C42000
    QuotaPeakPagedPoolUsage: 0x00004ED8
    QuotaPagedPoolUsage: 0x00004ED8
    QuotaPeakNonPagedPoolUsage: 0x000057A8
    QuotaNonPagedPoolUsage: 0x000057A8
    PagefileUsage: 0xA3A9B000
    PeakPagefileUsage: 0xA3A9B000

我跟踪了 Eigen 的内部分配器,它似乎确实“分配”了我在纸上计算的大小。然而,Eigen 的大部分动态向量都使用了aligned_alloc。这会造成如此严重的破坏吗?如果没有想到,您能否推荐其他地方来查找为什么会发生这种情况的问题?

我无法提供可编译的(在线)cpp 示例,但这是我正在做的事情的概要:

struct SSCCE_struct
{
    Eigen::VectorXd m_data;
};

typedef std::vector<SSCCE_struct*> TVector;

int main(int argc, char* argv[])
{
    TVector outputVertices;
    HANDLE bpcHandle;
    bpcHandle = CreateFileA("D:\\sample.bpc",              
        GENERIC_READ,          
        FILE_SHARE_READ,       
        NULL,                 
        OPEN_EXISTING,        
        FILE_ATTRIBUTE_NORMAL, 
        NULL);                 

    LARGE_INTEGER  len_li;
    GetFileSizeEx (bpcHandle, &len_li);
    INT64 len = len_li.QuadPart; //(len_li.u.HighPart << 32) | len_li.u.LowPart;

    unsigned long long noPoints = len / 20;
    unsigned long noPointsRead = 0;
    unsigned long long currPointIdx = 0;

    outputVertices.resize( noPoints );

    DebugTrace( "No points %lu \n", noPoints );

    float buffer[ 5 * 1024 ];
    DWORD noBytesRead = 0;
    do 
    {
        ReadFile(bpcHandle, buffer, sizeof(buffer), &noBytesRead, NULL);
        noPointsRead = noBytesRead / 20;
        for (unsigned long idx = 0; idx < noPointsRead; ++idx )
        {
            outputVertices[ currPointIdx + idx ] = new SSCCE_struct();

            outputVertices[ currPointIdx + idx ]->m_data.resize(5);

            for (unsigned kdx = 0; kdx < 5; ++kdx)
            {
                outputVertices[ currPointIdx + idx ]->m_data[ kdx ] = buffer[ 5 * idx + kdx ];
            }
        }

        currPointIdx += noPointsRead;

    } while (noBytesRead);


    CloseHandle(bpcHandle);
}
}

稍后编辑

我执行了大卫回答中指出的测试,解决方案是完全避免动态分配。您可以尝试多种组合,以下是所有这些组合的结果:

1.

struct SSCCE_struct
{
    Eigen::Matrix<double,1,5> m_data;
};

typedef std::vector<SSCCE_struct*> TVector;

产生 1.4 GB(1.1 GB 浪费)

2.

 struct SSCCE_struct
 {
    Eigen::VectorXd m_data;
 };

 typedef std::vector< SSCCE_struct* > TVector;

产生 2.5 GB(2.2 GB 浪费)

3.

struct SSCCE_struct
{
    Eigen::Matrix<double,1,5> m_data;
};

typedef std::vector<SSCCE_struct> TVector;

产生 381 GB(有 40 MB 的浪费 - 完全合理,也许是可预测的)。

【问题讨论】:

  • aligned_alloc 没有解释这一点。 SSCCE 会很好。你应该很容易做到。
  • @DavidHeffernan 我添加了一个pseudoSSCCE(恐怕不能那么容易地在线编译:D - 你需要数据、Eigen、winapi 等等)。
  • 你不需要让它在线编译。一个示例输入文件也很好。无论如何,我想知道你为什么使用VectorXd 而不是Vector5d。后者肯定会更有效率。
  • 这只是一个提示,但您可以使用Eigen::MapEigen::Matrix&lt;double,1,5&gt;(或5,1 ofc)交叉检查内存使用情况,以排除默认的特征动态分配器。 @DavidHeffernan 我不太确定。我认为 5 或 6 是他们衡量性能时的边界情况。他们建议对更长的向量使用动态。我可以检查参考文献,因为我对此记忆模糊,但我很确定我在某个地方读到过。
  • @luk32 对于内存,使用固定大小必须更有效。这样就可以避免堆分配开销。

标签: c++ winapi memory eigen allocator


【解决方案1】:

这里有很多指针,每个指针都有分配开销。指针指向小对象,因此开销很大。

最重要的是,动态分配的对象必然比固定大小的对象有更多的开销。这是因为固定大小的对象不需要存储矩阵维度。

以下是指针开销的来源:

  1. Eigen::VectorXd 使用动态分配的存储。这意味着一个指针。
  2. 您将对象存储在std::vector&lt;SSCCE_struct*&gt; 中。这是另一个指针,有开销。

存储这些对象最有效的方法是删除间接性。你可以通过切换到:

  1. Matrix&lt;double, 5, 1&gt;。这是一个固定大小的对象,因此没有间接性。更重要的是,如上所述,它不需要在运行时存储矩阵维度,因为它们在编译时是已知的。对于这样一个意义重大的小物体。
  2. 将对象存储在std::vector&lt;SSCCE_struct&gt; 中。同样,您失去了一层间接性。

通过这些更改,在我的机器上使用发布设置编译时,程序的内存使用量降至 383MB。这更符合您的期望。

最大的区别似乎在Eigen::VectorXd 和固定大小的对象之间。如果我使用Eigen::VectorXdstd::vector&lt;SSCCE_struct&gt;,那么内存使用量会跳转到 918MB。然后当我转到 std::vector&lt;SSCCE_struct*&gt; 时,它会进一步跳转到 1185MB。

这些测量将高度依赖于编译器。我用VS2013编译32位代码。

【讨论】:

  • 现在将对其进行测试。但是,内存(而不是速度)开销从何而来?开销实际上如此之高并没有任何意义。顺便说一句,在某些时候我将不得不使用SSCCE_struct*(指针),因为这是根据算法对数据进行加扰的基础到一些更复杂的逻辑。当然,我可以正常存储它们并传递引用..
  • 在我的机器上,跳跃是你结果的两倍多。我想知道它可能是什么:内存碎片..页面错误?真的很诡异,我不得不厚颜无耻地承认,低级的花絮不是我的面包和黄油。
  • 动态内存分配会带来开销。每个单独的内存块都有一个分配头。您可以在std::vector&lt;SSCCE_struct&gt;std::vector&lt;SSCCE_struct*&gt; 之间的差异中看到这一点。差异为 267MB。总共有 9,748,422 个元素,因此开销约为 28 个字节。这对我来说似乎很合理。不要分配很多很多小块的内存是信息。
  • 不会是页面错误。这将取决于编译器和运行时。我正在使用 VS2013。
  • 非常感谢,大卫!我会说不(我明天会接受答案) - 你帮了很多忙。唯一的难题是编译器实际上做了什么来产生如此巨大的变化,但这应该放在另一个问题中。
【解决方案2】:

我不允许发表评论,所以我会再发布一个答案,尽管我认为上述答案实际上解释了浪费内存的来源(大量分配)。

我知道你想处理很多 penta,所以你使用的是 struct SSCCE_struct 的向量,即

std::vector<SSCCE_struct*> TVector;

你考虑过使用

Eigen::Matrix< double, Dynamic, 5 > outputVertices;
outputVertices.resize( noPoints, 5 );

这样可以避免内存的浪费。我也会考虑将其用于矢量化(以帮助 eigen/compiler 更好地矢量化您对它们所做的任何事情),即使 5 对于矢量化 4 或 8 而言不是一个非常方便的数字。

编辑:当我点击帖子的那一刻,我意识到我迟到了 3 年......

【讨论】:

  • 感谢您的回答,即使已经很晚了(我最终解决了上述问题)。是的,您提出该建议是正确的。尽管如此,它仍然是一些人称之为面向数据的设计 (gamedevelopment.tutsplus.com/articles/…) 中的101 个。
  • 感谢您的精彩文章!
最近更新 更多