Нам нужно предварительно распределить. Но MATLAB не предопределяет предварительное распределение?

При тестировании any() коротких замыканий()! Я обнаружил следующее интересное поведение при предварительном распределении тестовой переменной:

test=zeros(1e7,1);
>> tic;any(test);toc
Elapsed time is 2.444690 seconds.
>> test(2)=1;
>> tic;any(test);toc
Elapsed time is 0.000034 seconds.

Однако, если я это сделаю:

test=ones(1e7,1);
test(1:end)=0;
tic;any(test);toc
Elapsed time is 0.642413 seconds.
>> test(2)=1;
>> tic;any(test);toc
Elapsed time is 0.000021 seconds.

Оказывается, это происходит потому, что переменная на самом деле не находится в ОЗУ, пока не будет полностью заполнена информацией, поэтому первый тест занимает больше времени, потому что ему необходимо выделить его. Я проверил это, посмотрев на память, используемую в диспетчере задач Windows.

Хотя это может иметь некоторый смысл (не инициализироваться до тех пор, пока это не понадобится), что меня немного сбило с толку, это следующий тест, где переменная заполняется в цикле for, и в какой-то момент выполнение останавливается.

test=zeros(1e7,1);

for ii=1:1e7
    test(ii)=1;
    if ii==1e7/2
        pause
    end
end

При проверке памяти, используемой MATLAB, я мог видеть, как при остановке она использовала только 50% требуемой памяти для test (если она была заполнена). Это можно воспроизвести с разным количеством памяти довольно солидно.

Интересно, что следующее также не выделяет всю матрицу.

test=zeros(1e7,1);
test(end)=1;

Я знаю, что MATLAB не динамически выделяет и не увеличивает размер test в цикле, поскольку это сделает очень медленные итерации конца (из-за высоких memcopys, которые понадобятся), и он также выделил бы весь массив в этом последнем тесте я предложил. Поэтому мой вопрос:

Что здесь происходит?

Кто-то предположил, что это может быть связано с виртуальной памятью против физической памяти и связано с тем, как ОС видит память. Не знаете, как это связано с первым испытанием, предложенным здесь. Любое дальнейшее объяснение было бы идеальным.

Выиграйте 10 x64, MATLAB 2017a

Ответы

Ответ 1

Это поведение не уникально для MATLAB. Фактически, MATLAB не имеет никакого контроля над ним, так как это вызывает Windows. Linux и MacOS показывают одинаковое поведение.

Я заметил это одно и то же в программе С много лет назад. Оказывается, это хорошо документированное поведение. Этот отличный ответ объясняет в деталях, как управление памятью работает в большинстве современных ОС (спасибо Amro за обмен ссылкой!). Прочтите, если у этого ответа недостаточно подробностей.

Во-первых, повторите эксперимент Андера в C:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main (void) {

   const int size = 1e8;

   /* For Linux: */
   // const char* ps_command = "ps --no-headers --format \"rss vsz\" -C so";
   /* For MacOS: */
   char ps_command[128];
   sprintf(ps_command, "ps -o rss,vsz -p %d", getpid());

   puts("At program start:");
   system(ps_command);

   /* Allocate large chunck of memory */

   char* mem = malloc(size);

   puts("After malloc:");
   system(ps_command);

   for(int ii = 0; ii < size/2; ++ii) {
      mem[ii] = 0;
   }

   puts("After writing to half the array:");
   system(ps_command);

   for(int ii = size/2; ii < size; ++ii) {
      mem[ii] = 0;
   }

   puts("After writing to the whole array:");
   system(ps_command);

   char* mem2 = calloc(size, 1);

   puts("After calloc:");
   system(ps_command);

   free(mem);
   free(mem2);
}

Этот код работает на совместимой с POSIX ОС (то есть на любой ОС, кроме Windows), но в Windows вы можете использовать Cygwin, чтобы стать (в основном) совместимым с POSIX. Возможно, вам придется изменить синтаксис команды ps зависимости от вашей ОС. Скомпилируйте с помощью gcc so.c -o so, запустите с ./so. Я вижу следующий вывод в MacOS:

At program start:
   RSS      VSZ
   800  4267728
After malloc:
   RSS      VSZ
   816  4366416
After writing to half the array:
   RSS      VSZ
 49648  4366416
After writing to the whole array:
   RSS      VSZ
 98476  4366416
After calloc:
   RSS      VSZ
 98476  4464076

Отображаются два столбца: RSS и VSZ. RSS означает "Размер резидентного набора", это объем физической памяти (ОЗУ), который использует программа. VSZ означает "Виртуальный размер", это размер виртуальной памяти, назначенный программе. Обе величины находятся в KiB.

Столбец VSZ показывает 4 GiB при запуске программы. Я не уверен, что это такое, это кажется сверху. Но значение растет после malloc и снова после calloc, оба раза с примерно 98 000 KiB (немного выше выделенных 1e8 байт).

Напротив, столбец RSS показывает увеличение только 16 KiB после того, как мы выделили 1e8 байт. После записи в половину массива мы используем более 5е7 байт используемой памяти, а после записи в полный массив у нас бит более 1 байта в байтах. Таким образом, память назначается по мере ее использования, а не когда мы сначала ее запрашиваем. Затем мы выделяем еще 1e8 байт с помощью calloc и не видим изменений в RSS. Обратите внимание, что calloc возвращает блок памяти, который инициализируется 0, точно так же, как и zeros MATLAB.

Я говорю о calloc потому что вполне вероятно, что zeros MATLAB реализованы через calloc.

Объяснение:

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

Ключ - выделенный жирным шрифтом текст выше: при использовании. Память, назначенная процессу, может фактически не существовать, пока процесс не попытается прочитать или записать на него. Вот почему мы не видим никаких изменений в RSS при распределении большого массива. Используемая память назначается физической памяти на страницах (блоки обычно 4 KiB, иногда до 1 MiB). Поэтому, когда мы пишем в один байт нашего нового блока памяти, назначается только одна страница.

Некоторые ОС, такие как Linux, будут даже "перекомпилировать" память. Linux будет присваивать больше виртуальной памяти процессам, чем имеет возможность помещать в физическую память, в предположении, что эти процессы не будут использовать всю память, которую они назначили в любом случае. Этот ответ расскажет вам больше об излишнем, чем вы захотите узнать.

Итак, что происходит с calloc, который возвращает ноль-инициализированную память? Это также объясняется в ответе, который я связывал ранее. Для небольших массивов malloc и calloc возвращают блок памяти из большего пула, полученного из ОС в начале программы. В этом случае calloc будет записывать нули во все байты, чтобы убедиться, что он инициализирован нулем. Но для больших массивов из ОС непосредственно получается новый блок памяти. ОС всегда выдает память, которая обнуляется (опять же, она не позволяет одной программе видеть данные из другой программы). Но поскольку память не получает физическое назначение до использования, обнуление также задерживается до тех пор, пока страница памяти не будет помещена в физическую память.

Вернуться к MATLAB:

Эксперимент выше показывает, что можно получить нулевой блок памяти -o ut в постоянное время и без изменения физического размера программной памяти. Это как MATLAB функция zeros выделяет память, не вы видите какие - либо изменения в памяти след MATLAB.

Эксперимент также показывает, что zeros выделяют полный массив (вероятно, через calloc), и этот объем памяти увеличивается только при использовании этого массива, по одной странице за раз.

В предопределяющем совете MathWorks говорится, что

вы можете улучшить время выполнения кода, предварительно распределив максимальное пространство, необходимое для массива.

Если мы выделяем небольшой массив, то хотим увеличить его размер, нужно выделить новый массив и скопировать данные. Как массив связан с ОЗУ, это не влияет на это, MATLAB видит только виртуальную память, у него нет контроля (или даже знания?) О том, где хранятся эти данные в физической памяти (ОЗУ). Все, что имеет значение для массива с точки зрения MATLAB (или любой другой программы), состоит в том, что массив является непрерывным блоком виртуальной памяти. Расширение существующего блока памяти не всегда (обычно не?) Возможно, и поэтому получается новый блок и копируются данные. Например, см. График в этом другом ответе: когда массив увеличен (это происходит при больших вертикальных шипах) данные копируются; чем больше массив, тем больше данных нужно скопировать.

Предораспределение позволяет избежать увеличения массива, поскольку мы делаем его достаточно большим для начала. Фактически, более эффективно создавать массив, который слишком велик для того, что нам нужно, поскольку часть массива, который мы не используем, на самом деле никогда не передается программе. То есть, если мы выделяем очень большой блок виртуальной памяти и используем только первые 1000 элементов, мы будем использовать только несколько страниц физической памяти.

Поведение calloc описанное выше, объясняет также это другое странное поведение функции zeros: для небольших массивов zeros дороже, чем для больших массивов, потому что небольшие массивы должны быть обнулены явно программой, тогда как большие массивы неявно обнуляются ОПЕРАЦИОННЫЕ СИСТЕМЫ.