三角形2d阵列比矩形花费更多的内存

时间:2012-11-29 04:12:51

标签: c++ memory scheduled-tasks dynamic-programming multidimensional-array

我正在为我的大学课程编写一个程序。它是2个处理器上简单版本的调度任务的动态编程算法的实现。因为它是一种记忆浪费的方法,我想到了它的一些改进。例如,一个不必存储整个S x n矩形阵列,其中S是所有任务的时间总和,n是任务数。因为在算法数据的第一次迭代中,只存储在n轴的小索引值中,我想我可以使我的数组成为一个三角形,即每个下一个子数组都是一定数量的元素。

然后我查看了任务管理器的内存使用情况,我很震惊。矩形阵列版本需要980KB。带有三角形阵列的版本(所以较小的一个)花费了近15MB!也许我不知道系统使用的内存分配方式,或者我有妄想。或者我在代码中犯了一些愚蠢的错误。但我敢打赌,我不知道什么。有人能让我高兴吗?

这是我的代码:

#include <iostream>
#include <fstream> 
#include <conio.h>
#include <stack>

using namespace std;

void readTasks(char* filename, int*& outTaskArray, int& outTaskCount)
{
    ifstream input = ifstream();
    input.open(filename, ios::in);

    input >> outTaskCount;
    outTaskArray = new int[outTaskCount];
    for (int i = 0; i < outTaskCount; ++i)
    {
        input >> outTaskArray[i];
    }

    input.close();
}

void coutTasks(int* taskArray, int taskCount)
{
    cout << taskCount << " tasks:\n";
    for (int i = 0; i < taskCount; ++i)
    {
        cout << i << ": " << taskArray[i] << endl;
    }
}

void Scheduling2(int* taskArray, int taskCount, int memorySaving, 
    stack<int>*& outP1Stack, stack<int>*& outP2Stack)
{
    bool** scheduleArray = new bool*[taskCount];
    int sum;

    // I know that construction below is ugly cause of code repetition.
    // But I'm rather looking for performance, so I try to avoid e.g. 
    // checking the same condition too many times.
    if (memorySaving == 0)
    {   
        sum = 0;
        for (int i = 0; i < taskCount; ++i)
        {
            sum += taskArray[i];
        }

        scheduleArray[0] = new bool[sum + 1];
        for (int j = 0; j < sum + 1; ++j)
        {
            scheduleArray[0][j] = j == 0 || j == taskArray[0];
        }
        for (int i = 1; i < taskCount; ++i)
        {
            scheduleArray[i] = new bool[sum + 1];
            for (int j = 0; j < sum + 1; ++j)
            {
                scheduleArray[i][j] = scheduleArray[i - 1][j] || 
                    j >=  taskArray[i] && 
                    scheduleArray[i - 1][j - taskArray[i]];
            }
        }

        getch(); // I'm reading memory usage from Task Manager when program stops here

        int halfSum = sum >> 1;
        while (!scheduleArray[taskCount - 1][halfSum]) --halfSum;

        for (int i = taskCount - 1; i > 0; --i)
        {
            if (scheduleArray[i - 1][halfSum])
                outP1Stack->push(i);
            else if (scheduleArray[i - 1][halfSum - taskArray[i]])
            {
                outP2Stack->push(i);
                halfSum -= taskArray[i];
            }
        }
        if (halfSum) outP2Stack->push(0);
            else outP1Stack->push(0);
    }
    else if (memorySaving == 1)
    {   
        sum = 0;
        for (int i = 0; i < taskCount; ++i)
        {
            sum += taskArray[i];

            scheduleArray[0] = new bool[sum + 1];
            for (int j = 0; j < sum + 1; ++j)
            {
                scheduleArray[0][j] = j == 0 || j == taskArray[0];
            }
            for (int i = 1; i < taskCount; ++i)
            {
                scheduleArray[i] = new bool[sum + 1];
                        for (int j = 0; j < sum + 1; ++j)
                {
                    scheduleArray[i][j] = scheduleArray[i - 1][j] || 
                        j >= taskArray[i] && 
                        scheduleArray[i - 1][j - taskArray[i]];
                }
            }
        }

        getch(); // I'm reading memory usage from Task Manager when program stops here

        int halfSum = sum >> 1;
        while (!scheduleArray[taskCount - 1][halfSum]) --halfSum;

        for (int i = taskCount - 1; i > 0; --i)
        {
            if (scheduleArray[i - 1][halfSum])
                outP1Stack->push(i);
            else if (scheduleArray[i - 1][halfSum - taskArray[i]])
            {
                outP2Stack->push(i);
                halfSum -= taskArray[i];
            }
        }
        if (halfSum) outP2Stack->push(0);
        else outP1Stack->push(0);
    }

    for (int i = 0; i < taskCount; ++i)
    {
        delete[] scheduleArray[i];
    }
    delete[] scheduleArray;
}

int main()
{
    char* filename = "input2.txt";
    int memorySaving = 0; //changing to 1 in code when testing memory usage

    int* taskArray; // each number in array equals time taken by task
    int taskCount;
    readTasks(filename, taskArray, taskCount);

    coutTasks(taskArray, taskCount);

    stack<int>* p1Stack = new stack<int>();
    stack<int>* p2Stack = new stack<int>();

    Scheduling2(taskArray, taskCount, memorySaving, p1Stack, p2Stack);

    cout << "\np1: ";
    while (p1Stack->size())
    {
        cout << p1Stack->top() << ", ";
        p1Stack->pop();
    }
    cout << "\np2: ";
    while (p2Stack->size())
    {
        cout << p2Stack->top() << ", ";
        p2Stack->pop();
    }

    delete p1Stack;
    delete p2Stack;

    delete[] taskArray;
    return 0;
}

2 个答案:

答案 0 :(得分:2)

该死的,我是瞎子。我有一个很大的内存泄漏,我没有看到。我只是查看了执行的部分,当memorySaving == 1注意到我正在分配(上帝知道为什么)我的数组的每一行taskCount次......这完全不是我的意思,当我是写这个。好。那是深夜。

很抱歉打扰你们。问题应该结束。

答案 1 :(得分:1)

由于我的建议可以解决你的问题,如果你把它们带走了(我怀疑),我会给他们一个答案!

  

但是为什么在这种情况下使用vector会帮助我?我不需要使用它的任何功能。

是的,你做到了!您需要其中一个最重要的“功能”......阵列内存块的自动管理。请注意,如果您的声明为vector< vector<bool> > scheduleArray,则无法发生泄露。您的代码中不会有任何新内容或删除内容......怎么可能?

使用vector的其他好处:

  • 您不能在指针上意外执行delete而不是delete[]

  • 它可以进行边界检查(如果你启用它,你应该在你的调试版本中......只用vector<int> v; v[0] = 1;尝试测试,以确保你打开它。)

  • Vector知道它所持有的元素数量,因此您不必遇到必须传递taskCount等参数的情况。这消除了另一个您有机会在记账中出错的地方。 (例如,如果从向量中删除元素并忘记在计数变量中反映出来该怎么办?)

您的评论的答案:

  

移位操作的速度是否比除以2更快?

没有

如果您在原始程序集中进行编码,那么有时可能会在某些体系结构上进行编码。但是大多数情况下,整数除法和位移都会花费整个周期。而且我确信那里存在一些古怪的架构,它可以分裂得比它可以移动得快。

请记住,这是C ++,而不是汇编。最好保持代码清晰,并信任优化器做正确的事情。例如,如果SHIFT和DIV都需要一个指令周期,但是如果你因为管道的某些事情而处于严格的循环中,你可以通过交替使用更快的速度?

  

memorySaving将拥有比两个更多的值。

然后使用枚举类型。

  

std :: vector O(1)是否按索引访问每个元素,因为数组有?

是的,正如您所发现的那样。有small amount of per-vector overhead in terms of storage(因编译器而异)。但正如我上面提到的,这是一个很小的代价。此外,您通常可能首先在阵列外部的某个变量中跟踪长度。

  

至于指向堆栈的指针 - 通常情况下动态内存分配通常更好,因为我可以自己决定何时释放内存?

通常更好,正是因为这个原因。如果你负责决定何时自己释放记忆,那么你可以放弃管理这个责任。

因此,只要有可能,您应该让C ++的范围处理对象的生命周期。有时制作一个在其创建范围之外存活的动态对象是无法避免的,但这就是Modern C ++有智能指针的原因。使用'em!

http://en.wikipedia.org/wiki/Smart_pointer

例如,如果readTasks返回shared_ptr< vector<int> >,则filename[0] = 'I';会更干净,更安全。

  

为什么我要使用std :: string,如果我不使用它支持的任何函数,并且char *和std :: string一样好吗?

养成不使用它的习惯,原因是与vector的上述参数并行。例如,边界检查。另外,测验问题:如果您想要大写“input2.txt”并说{{1}},您认为会发生什么?

当您完成我的所有建议后,您可以查看boost::dynamic_bitset。 : - )