Массив Triangle 2d стоит больше памяти, чем прямоугольный

Я пишу программу на занятия в колледже. Это реализация алгоритма динамического программирования для простой версии задач планирования на 2 процессорах. Потому что это бесполезный метод памяти, я подумал о некоторых улучшениях. Например, не нужно хранить целый прямоугольный массив S x n, где S — сумма времен всех задач, а n — количество задач. Поскольку в первых итерациях алгоритма данные будут храниться только в небольших значениях индекса по оси n, я подумал, что могу сделать свой массив треугольником, то есть каждый следующий подмассив будет на определенное количество элементов длиннее.

Затем я посмотрел в диспетчере задач на использование памяти, и я был в шоке. Версия с прямоугольным массивом заняла 980КБ. Версия с массивом треугольников (поэтому меньшего размера) заняла почти 15 МБ! Возможно я не знаю что-то о способах распределения памяти, используемых системой, или у меня есть заблуждение. Или я сделал глупую ошибку в своем коде. Но держу пари, что я чего-то не знаю. Может кто-то меня просветить?

Вот мой код:

#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

Решение

Черт возьми, я слепой. У меня чертовски большая утечка памяти, и я этого не видел. Я просто посмотрел на исполненную часть, когда memorySaving == 1 и заметил, что я выделяю (бог знает почему) каждую строку моего массива taskCount времена … Это совсем не то, что я имел в виду, когда писал это. Что ж. Была поздняя ночь.

Извините, что беспокою вас всех. Вопрос должен быть закрыт.

2

Другие решения

Поскольку мои предложения исправили бы вашу проблему, если бы вы их приняли (как я и подозревал), Я сделаю им ответ!

Но почему использование вектора помогло бы мне в этом случае? Мне не нужно использовать какие-либо из его функций.

Да вы сделали! Вам нужна была одна из его самых важных «функций» … автоматическое управление блоком памяти массива. Обратите внимание, что если ваша декларация была vector< vector<bool> > scheduleArray, вы не может была утечка. В вашем коде не было бы ничего нового или удаленного … как это было бы возможно?

Другие преимущества использования вектора:

  • Вы не можете случайно сделать delete вместо delete[] на указатель

  • Это может сделать проверку границ (если вы включите его, что вы должны делать в своих отладочных сборках … попробуйте выполнить тест с vector<int> v; v[0] = 1; чтобы убедиться, что это включено.)

  • Вектор знает, сколько элементов он содержит, поэтому вы не столкнетесь с ситуациями, когда вам нужно передать такие параметры, как taskCount, Это исключает еще одно место, где у вас есть возможность ошибиться в бухгалтерии. (например, что если вы удалите элемент из вектора и забудете отразить это в переменной count?)

Ответы на ваши комментарии:

Разве операция сдвига не быстрее деления на два?

Нет.

Если вы пишете код в необработанной сборке, то иногда это может происходить на определенных архитектурах. Но по большей части целочисленное деление и сдвиг битов в любом случае стоят всего цикла. И я уверен, что существует какая-то дурацкая архитектура, которая может делиться быстрее, чем сдвигаться.

Помните, что это C ++, а не сборка. Лучше всего, чтобы ваш код был чистым и чтобы оптимизатор действовал правильно. Например, что, если SHIFT и DIV оба выполняют один цикл инструкций, но вы можете получить большую скорость, чередуя то, что вы используете, если находитесь в узком цикле из-за чего-то в конвейере?

memorySaving будет иметь больше значений, чем просто два.

Затем используйте перечислимый тип.

Имеет ли std :: vector O (1) время доступа к каждому элементу по индексу, как у массива?

Да, как вы обнаружили. Eсть небольшое количество накладных расходов на каждый вектор с точки зрения хранения (который зависит от компилятора). Но, как я уже говорил выше, это небольшая цена. Кроме того, вы, вероятно, обычно отслеживали длину в некоторой переменной за пределами массива.

Что касается указателей на стеки — разве динамическое распределение памяти вообще не лучше, так как я могу сам решить, когда освободить память?

Это вообще не лучше, именно по этой причине. Если вы сами ответственны за решение, когда освободить память, тогда вы можете отказаться от управления этой ответственностью.

Поэтому, где это возможно, вы должны позволить области видимости C ++ обрабатывать время жизни ваших объектов. Иногда просто невозможно избежать создания динамического объекта, который выходит за рамки его создания, но именно поэтому в Modern C ++ есть умные указатели. Используй их!

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

Например, ваш readTasks будет чище и безопаснее, если он вернет shared_ptr< vector<int> >,

Зачем мне использовать std :: string, если я не использую какие-либо функции, которые он поддерживает, и char * здесь так же хорош, как std :: string?

Чтобы привыкнуть не использовать его по причинам, параллельным приведенным выше аргументам для вектора. Например, проверка границ. Кроме того, вопрос викторины: как вы думаете, что произойдет, если вы захотите использовать прописную букву «input2.txt» и сказали filename[0] = 'I';?

Когда вы закончите с реализацией всех моих предложений, тогда вы можете посмотреть на повышение :: dynamic_bitset. 🙂

1

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector