Эффективный для памяти способ усечения большого массива в Matlab

У меня есть большой массив (multi-GB) в Matlab, который я хочу обрезать¹. Наивно, я думал, что усечение не может потребовать много памяти, но потом я понял, что он, вероятно, может:

>> Z = zeros(628000000, 1, 'single');
>> Z(364000000:end) = [];
Out of memory. Type HELP MEMORY for your options.

Если Matlab не делает некоторые умные оптимизации, перед усечением Z этот код фактически создает массив (типа double!) 364000000:628000000. Мне не нужен этот массив, поэтому я могу сделать это:

>> Z = Z(1:363999999);

В этом случае второй пример работает и подходит для моей цели. Но почему это работает? Если Z(364000000:end) = 0 выходит из строя из-за памяти, необходимой для промежуточного массива 364000000:628000000, то почему не Z = Z(1:363999999) не удается из-за памяти, необходимой для промежуточного массива 1:363999999, что больше? Конечно, мне не нужен этот промежуточный массив, и я был бы доволен решением, которое усекает мой массив без промежуточного массива, или, если это не так, если Matlab оптимизирует определенный метод.

  • Есть ли способ обрезать массив без создания промежуточного массива индексирования?
  • Если нет, то какой из вышеперечисленных методов более эффективен с точки зрения памяти, чем другой (кажется, что он есть)? Если да, то почему? Действительно ли Matlab создает промежуточные массивы в обоих примерах?

¹Reason: Я обрабатываю данные, но не знаю, как много предварительно распределить. Я понимаю, что часто выделяю слишком много. Я выбираю размер блока на основе доступной памяти, потому что расщепление в меньшем количестве фрагментов означает более быстрый код. Поэтому я хочу избежать ненужного использования памяти. См. Также этот пост при распределении по фрагменту.

Ответы

Ответ 1

Я запускал оба примера на машине с 24 ГБ ОЗУ с profile('-memory','on');. Эта опция профилировщика отображает выделенную и освобожденную память в каждой строке кода. Предполагается, что это не брутто, а чистые суммы. Я проверил с простой функцией, которая имеет net 0 бесплатно и alloc, и она сообщила о брутто-суммах. Однако представляется вероятным, что встроенные команды без кода .m для их резервного копирования не предоставляют мелкозернистую отчетность памяти профилировщику.

Я провел пару тестов для следующего кода:

% truncTest.m
N = 628000000;
M = 364000000;

clear Z
Z = zeros(N,1,'single');
Z(M:end) = [];
Z(1) % just because

clear Z
Z = zeros(N,1,'single');
Z = Z(1:M);
Z(1)

Для того, что они стоят, результаты профилирования памяти для этих N и M:

enter image description here

Ну, обе линии выглядят одинаково с точки зрения выделенной и освобожденной памяти. Может быть, это не вся правда.

Итак, из любопытства я уменьшил M до 200 (всего 200!) без изменения N, сделал profile clear и повторил. Профилирование:

enter image description here

Интересно, что Z=Z(1:M); сейчас практически мгновенно, а Z(M:end)=[]; - немного быстрее. Оба освобождают около 2,4 ГБ памяти, как и ожидалось.

Наконец, если мы идем в другом направлении и установите M=600000000;:

enter image description here

Теперь даже Z=Z(1:M); работает медленно, но примерно дважды с точностью Z(M:end)=[];.

Это говорит о том, что

  • Z=Z(1:M); просто захватывает указанные элементы, сохраняет их в новом буфере или временной переменной, освобождает старый буфер и назначает новый/временный массив Z. Я смог сделать мою более слабую машину размером 4 ГБ с 2,45 секунды до того, чтобы пробить файл страницы в течение 5 минут, просто увеличив M и оставив N один. Определенно предпочитайте этот вариант для небольшого M/N, возможно, во всех случаях.
  • Z(M:end)=[]; всегда перезаписывает буфер, а время выполнения увеличивается с помощью M. На самом деле всегда медленнее и, кажется, увеличивается экспоненциально, в отличие от Z=Z(1:M);.
  • Профилирование памяти не дает мелкой информации об этих встроенных операциях и не должно быть неверно истолковано как предоставление полной памяти, освобожденной и распределенной по исполнению команд, а скорее изменение сети.

ОБНОВЛЕНИЕ 1. Просто для удовольствия я приурочил тесты к диапазону значений M:

enter image description here

Очевидно, более информативный, чем профилирование. Оба метода не являются no-ops, но Z=Z(1:M); является самым быстрым, но он может использовать почти вдвое больше памяти Z для M/N около 1.

ОБНОВЛЕНИЕ 2:

Относительно неизвестный feature, называемый mticmtoc), был доступен в 32-битной Windows до R2008b. Я все еще устанавливаю его на одной машине, поэтому я решил посмотреть, дает ли это более глубокое понимание, понимая, что (а) многое изменилось с тех пор и (б) это совершенно другой менеджер памяти, используемый в 32-битном MATLAB. Тем не менее, я уменьшил размер теста до N=128000000; M=101000000; и посмотрел. Во-первых, feature mtic для Z=Z(1:M-1);

>> tic; feature mtic; Z=Z(1:M-1); feature mtoc, toc

ans = 

      TotalAllocated: 808011592
          TotalFreed: 916009628
    LargestAllocated: 403999996
           NumAllocs: 86
            NumFrees: 77
                Peak: 808002024

Elapsed time is 0.951283 seconds.

Очистка, воссоздание Z, другим способом:

>> tic; feature mtic; Z(M:end) = []; feature mtoc, toc

ans = 

      TotalAllocated: 1428019588
          TotalFreed: 1536018372
    LargestAllocated: 512000000
           NumAllocs: 164
            NumFrees: 157
                Peak: 1320001404

Elapsed time is 4.533953 seconds.

В каждой метрике (TotalAllocated, TotalFreed, NumAllocs и т.д.), Z(M:end) = []; менее эффективен, чем Z=Z(1:M-1);. Я ожидаю, что можно узнать, что происходит в памяти, исследуя эти числа для этих значений N и M, но мы будем гадать о старом MATLAB