0

tl:dr 我需要一种方法来更好地管理 C++ 中的内存,同时保留大型数据集。

我目前正在创建一个程序来输出我以后项目所需的数据库,并且我正在努力控制内存。我将程序写入功能级别,以小规模输出我需要的数据集,但是为了将大小增加到我需要的位置并使其保持现实,我需要增加迭代次数。问题是当我这样做时,我的计算机(4gb)内存不足,它必须开始分页,这大大减慢了处理速度。

基本大纲是我正在创建商店,然后为所述商店创建一年的交易数据。创建商店时,会生成一个数字列表,代表交易的每日销售目标,然后随机生成交易,直到达到该数字。这种方法给出了一些我很满意的很好的有机结果。不幸的是,所有这些交易都必须存储在内存中,直到它们输出到我的文件中。

创建事务时,它们会临时存储在一个向量中,在我将向量的副本存储在我的永久存储位置后,我会在该向量上执行 .clear()。

我已经开始尝试移动到 unique_ptr's 进行临时存储,但我不确定它们是否在从生成我的数据的函数返回时被正确删除。

代码是这样的(我删掉了一些与手头问题无关的多余代码)

void store::populateTransactions() {
    vector<transaction> tempVec;
    int iterate=0, month=0;
    double dayTotal=0;
    double dayCost=0;
    int day=0;
for(int i=0; i<365; i++) {
    if(i==dsf[month]) {
        month++;
        day=0;
    }
    while(dayTotal<dailySalesTargets[i]) {
        tempVec.push_back(transaction(2013, month+1, day+1, 1.25, 1.1));
        dayTotal+=tempVec[iterate].returnTotal();
        dayCost+=tempVec[iterate].returnCost();
        iterate++;
    }
        day++;

        dailyTransactions.push_back(tempVec);
        dailyCost.push_back(dayCost);
        dailySales.push_back(dayTotal);
        tempVec.clear();
        dayTotal = 0;
        dayCost = 0;
        iterate = 0;
    }
}

transaction::transaction(int year, int month, int day, double avg, double dev) {
    rng random;
    transTime = &testing;
    testing = random.newTime(year, month, day);
    itemCount = round(random.newNum('l', avg, dev,0));
    if(itemCount <= 0) {
        itemCount = 1;
    }

    for(int i=0; i<itemCount; i++) {
        int select = random.newNum(0,libs::products.products.size());
        items.push_back(libs::products.products[select]);
        transTotal += items[i].returnPrice();
        transCost += items[i].returnCost();
    }
}
4

2 回答 2

3

您遇到内存问题的原因是,当您将元素添加到向量时,它最终必须调整其内部缓冲区的大小。这需要分配一个新的内存块,将现有数据复制到新成员,然后删除旧缓冲区。

由于您知道向量将预先保存的元素数量,因此您可以调用向量reserve()成员函数来提前分配内存。这将消除您现在无疑会遇到的不断调整大小的问题。

例如,在构造函数中,transaction您将在将数据添加到向量的循环之前执行以下操作。

items.reserve(itemCount);

store::populateTransactions()您应该计算向量将保存和调用的元素总数,tempVec.reserve()如上所述。还要记住,如果您使用局部变量来填充向量,您最终需要复制它。这将导致与目标向量在复制内容之前需要分配内存相同的问题(除非您使用 C++11 中可用的移动语义)。如果需要将数据返回给调用函数(而不是作为 的成员变量store),您应该通过引用将其作为参数。

void store::populateTransactions(vector<transaction>& tempVec)
{
    //....
}

如果提前确定元素数量不切实际,则应考虑std::deque改用。来自cppreference.com

与 std::vector 不同,双端队列的元素不是连续存储的:典型的实现使用一系列单独分配的固定大小数组。

双端队列的存储会根据需要自动扩展和收缩。deque 的扩展比 std::vector 的扩展便宜,因为它不涉及将现有元素复制到新的内存位置。

关于Rafael Baptista关于调整大小操作如何分配内存的评论,下面的示例应该让您更好地了解它发生了什么。列出的内存量是调整大小期间所需的内存量

#include <iostream>
#include <vector>

int main ()
{
    std::vector<int>    data;

    for(int i = 0; i < 10000001; i++)
    {
        size_t oldCap = data.capacity();
        data.push_back(1);
        size_t newCap = data.capacity();
        if(oldCap != newCap)
        {
            std::cout
                << "resized capacity from "
                << oldCap
                << " to "
                << newCap
                << " requiring " << (oldCap + newCap) * sizeof(int)
                << " total bytes of memory"
                << std::endl;
        }
    }
    return 0;
}

使用 VC++10 编译时,将 1,000,001 个元素添加到向量时会生成以下结果。这些结果特定于 VC++10,并且在std::vector.

调整容量从 0 到 1 需要 4 个总字节的内存
调整容量从 1 到 2 需要 12 个总字节的内存
调整容量从 2 到 3 需要 20 个总字节的内存
调整容量从 3 到 4 需要 28 个总字节的内存
调整容量从 4 到 6 需要 40 总字节的内存
调整容量 从 6 到 9 需要 60 总字节的内存
调整容量从 9 到 13 需要 88 总字节的内存
调整容量从 13 到 19 需要 128 总字节的内存

……剪……

调整容量从 2362204 到 3543306 需要 23622040 总字节内存
调整容量从 3543306 到 5314959 需要 35433060 总字节内存
调整容量从 5314959 到 7972438 需要 53149588 总字节内存
调整容量从 7972438 到 119380652 总字节数 79724 内存

于 2013-06-01T19:32:50.747 回答
1

这个很有趣!我能想到的一些快速评论。

一个。STL clear() 并不总是立即释放内存。相反,您可以使用std::vector<transaction>().swap(tmpVec);.

湾。如果您使用的是具有 C++11 vector::emplace_back 的编译器,那么您应该删除 push_back 并使用它。它应该在内存和速度上都有很大的提升。使用 push_back,您基本上拥有两个相同数据的副本,并且您可以任由分配器将其返回给操作系统。

C。有什么理由不能每隔一段时间将 dailyTransactions 刷新到磁盘?您始终可以序列化向量并将其写入磁盘,清除内存,您应该会再次恢复正常。

d。正如其他人指出的那样,储备也应该有很大帮助。

于 2013-06-01T23:11:03.263 回答