Почему эта программа занимает слишком много памяти?

#c #recursion #memory-management #memory-optimization

#c #рекурсия #управление памятью #оптимизация памяти

Вопрос:

Я решаю задачу из школьного конкурса по программированию. Вот краткое описание:

У нас есть сетка высотой h и шириной w. Сетка заполнена символами ‘#’ и ‘.’. Восьмиугольники представляют землю, а точки представляют воду. Гарантируется, что первая и последняя строка и столбец сетки будут состоять из точек. Связанные октоторпы образуют острова, на которых могут быть озера, и каждое озеро также может иметь острова, и на этих островах могут быть озера и так далее… У каждого острова есть градус, который определяется как количество озер, которые нужно пересечь, чтобы добраться до него, если начинать с воды на краях сетки (эта вода рассматривается как море и исключается из числа озер, которые вы должны пересечь, чтобы добраться до острова). Найдите максимальную степень среди островов.

Я запускаю свою программу на школьном сервере и получаю сообщение о превышении лимита памяти, потому что программа предположительно занимает более 300 МБ памяти. Вот программа:

 #include <stdio.h>

char a[3010][3010];
int degree[3010][3010];
bool visited[3010][3010];
int h, w;

void searchSame(int x, int y, int d)
{
    degree[x][y] = d;

    if(x   1 < h amp;amp; a[x   1][y] == a[x][y] amp;amp; degree[x   1][y] == -1)
        searchSame(x   1, y, d);

    if(x - 1 >= 0 amp;amp; a[x - 1][y] == a[x][y] amp;amp; degree[x - 1][y] == -1)
        searchSame(x - 1, y, d);

    if(y   1 < w amp;amp; a[x][y   1] == a[x][y] amp;amp; degree[x][y   1] == -1)
        searchSame(x, y   1, d);

    if(y - 1 >= 0 amp;amp; a[x][y - 1] == a[x][y] amp;amp; degree[x][y - 1] == -1)
        searchSame(x, y - 1, d);
}

void search(int x, int y, int d)
{
    visited[x][y] = true;
    
    if(degree[x][y] == -1)
        searchSame(x,y,d);

    if(x   1 < h amp;amp; a[x   1][y] == a[x][y] amp;amp; !visited[x   1][y])
        search(x   1, y, d);
    else if(x   1 < h amp;amp; !visited[x   1][y])
        search(x   1, y, degree[x][y]   1);

    if(x - 1 >= 0 amp;amp; a[x - 1][y] == a[x][y] amp;amp; !visited[x - 1][y])
        search(x - 1, y, d);
    else if(x - 1 >= 0 amp;amp; !visited[x - 1][y])
        search(x - 1, y, degree[x][y]   1);

    if(y   1 < w amp;amp; a[x][y   1] == a[x][y] amp;amp; !visited[x][y   1])
        search(x, y   1, d);
    else if(y   1 < w amp;amp; !visited[x][y   1])
        search(x, y   1, degree[x][y]   1);

    if(y - 1 >= 0 amp;amp; a[x][y - 1] == a[x][y] amp;amp; !visited[x][y - 1])
        search(x, y - 1, d);
    else if(y - 1 >= 0 amp;amp; !visited[x][y - 1])
        search(x, y - 1, degree[x][y]   1);
}

int main()
{
    int mx = 0;
    scanf("%d %d",amp;h,amp;w);

    for(int i = 0; i < h; i  )
        scanf("%s",a[i]);

    for(int i = 0; i < h; i  )
    {
        for(int j = 0; j < w ; j  )
            degree[i][j] = -1;
    }   

    search(0,0,1);

    for(int i = 0; i < h; i  )
    {
        for(int j = 0; j < w ; j  )
        {
            if(degree[i][j] > mx)
                mx = degree[i][j];
        }
    }

    printf("%d",mx/2 - 1);
}
  

Значения h и w варьируются от 3 до 3000. Почему эта программа занимает так много памяти? Может быть, это как-то связано с рекурсивными функциями? Как я могу улучшить управление памятью?

Комментарии:

1. Добро пожаловать в Code Review! Этот вопрос привлекает голоса «вниз» и «закрыть», потому что он не по теме — мы не отвечаем на вопросы о том, как исправить код (или как понять код) — мы рассматриваем только рабочий код, автором / сопровождающим которого является постер, и который постер понимает. Подробности см. на странице по теме .

2. @Dannnno Но разве это не то, что я делаю? Я написал этот код, а не кто-то другой. Я понимаю этот код. Моя программа работает отлично, она просто занимает слишком много памяти. Я спрашиваю о том, почему требуется так много памяти, чтобы знать, как улучшить управление памятью.

3. Перейдите от рекурсивного к итеративному, используя некоторую компактную форму, чтобы отслеживать, где вы были и где вы остановились.

4. Программа потенциально выполняет миллионы вложенных рекурсивных вызовов. Это не поддерживается по умолчанию ни одной обычной ОС.

5. Я попробовал вашу программу, она выполняет сегментацию на глубине около 200 000 вызовов с настройками размера стека по умолчанию и на глубине 3 000 000 с максимальными настройками.

Ответ №1:

int degree[3010][3010]; резервирует пространство для 3010 * 3010 int сек, то есть более 9 миллионов. Если вы используете 32-битную систему, это означает, что вам нужно 9,06 миллионов раз по 4 байта = 36,2 МБ только для этого массива; в 64-битной системе (стандартной на сегодняшний день) для этого требуется 72,5 МБ.
Добавьте место для двух других, и вы поймете, почему программе требуется так много места.

Комментарии:

1. » в 64-битной системе (стандартной на сегодняшний день) ей требуется 72,5 МБ » — что? Почему? Какая система использует 8-байтовые int секунды? Никогда не слышал о таком.

2. Как размеры массивов в сумме превышают 300 Мбайт? Прежде всего, есть аргумент Fureeish. Во-вторых, даже если бы у меня было три таких массива целых чисел и даже если бы вы были правы относительно размера целых чисел, 72,5 МБ * 3 = 217,5 МБ. В-третьих, у меня есть только один массив целых чисел. Два других массива char и bool намного меньше. Я действительно не понимаю, как размеры массивов составляют более 300 Мбайт. Я что-то упускаю?

3. Для быстрого доступа компилятор помещает каждый bool в int = 4 байта. В 64-разрядной системе каждому int присваивается (быстро адресуемая) граница в 8 байт, что означает, что даже bool использует 8 байт. Вы можете контролировать это с помощью флагов компилятора — упаковка bool s в байты или даже биты делает программу намного меньше (и намного медленнее). И, наконец, есть некоторые другие накладные расходы, такие как стандартные библиотеки и сам код.

4. Если вы в это не верите, просто измените один из массивов на другой размер и проверьте, как исполняемый файл сжимается.

5. Я уменьшил массив целых чисел до высоты 1 и ширины 1. Использование памяти уменьшилось всего на 34,5 Мбайт. Однако, когда я изменил массив bool на тот же размер, использование памяти резко сократилось. Так что, скорее всего, это как-то связано с рекурсивными функциями.