4

我正在为我的大学课程编写一个程序。它是动态规划算法的实现,用于 2 个处理器上的简单版本的调度任务。因为这是一种浪费内存的方法,我想到了一些改进。例如,不必存储整个 S xn 矩形数组,其中 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;
}
4

2 回答 2

2

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

很抱歉打扰大家。问题应该关闭。

于 2012-11-29T11:49:14.867 回答
1

如果您接受了我的建议(正如我所怀疑的那样) ,因为我的建议会解决您的问题,所以我会给他们一个答案!

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

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

使用矢量的其他好处:

  • 你不能不小心在指针上做 adelete而不是delete[]

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

  • Vector 知道它拥有多少个元素,因此您不会遇到必须传递参数的情况,例如taskCount. 这消除了您有机会在簿记中犯错误的另一个地方。 (例如,如果您从向量中删除一个元素并忘记将其反映在 count 变量中怎么办?)

回复您的评论:

移位操作不是比除以二更快吗?

不。

如果您在原始汇编中进行编码,那么有时在某些架构上可能是这样。但在大多数情况下,整数除法和位移都需要花费整个周期。而且我确信存在一些古怪的架构,它们的分裂速度比它移动的速度快。

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

memorySaving 将有更多的价值,而不仅仅是两个。

然后使用枚举类型。

有 std::vector O(1) 通过索引访问每个元素的时间,就像数组一样?

是的,正如你所发现的。在存储方面存在少量的每个向量开销(因编译器而异)。但正如我上面提到的,这是一个很小的代价。此外,您可能通常首先跟踪数组外部某个变量的长度。

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

正是因为这个原因,它通常不会更好。如果您自己负责决定何时释放内存,那么您可以放弃管理该责任。

所以只要有可能,你应该让 C++ 的作用域来处理你的对象的生命周期。有时无法避免创建一个在其创建范围之外存活的动态对象,但这就是现代 C++ 具有智能指针的原因。使用它们!

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

例如,readTasks如果它返回一个shared_ptr< vector<int> >.

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

养成不使用它的习惯,原因与上述矢量论点相似。例如,边界检查。另外,测验问题:如果您想将“input2.txt”大写并说,您认为会发生什么filename[0] = 'I';

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

于 2012-11-29T13:07:33.520 回答