Функция Matlab обрабатывает рабочее пространство shenanigans

Короче: существует ли элегантный способ ограничить область анонимных функций, или же Matlab в этом примере разбит?

У меня есть функция, которая создает дескриптор функции, который будет использоваться в решателе сети труб. В качестве входных данных требуется состояние сети, которое включает в себя информацию о трубах и их соединениях (или ребрах и вершинах, если необходимо), строит большую строку, которая возвращает большую матрицу, когда в форме функции, и "анализирует" эту строку для создания дескриптора.

function [Jv,...] = getPipeEquations(Network)
... %// some stuff happens here

Jv_str = ['[listConnected(~endNodes,:)',...
    ' .* areaPipes(~endNodes,:);\n',...
    anotherLongString,']'];

Jv_str = sprintf(Jv_str); %// This makes debugging the string easier

eval(['Jv = @(v,f,rho)', Jv_str, ';']);

Эта функция работает по назначению, но всякий раз, когда мне нужно сохранять последующие структуры данных, содержащие этот дескриптор функции, для этого требуется смешной объем памяти (150 МБ) - по совпадению примерно столько же, сколько весь Matlab рабочей области во время создания этой функции (~ 150 МБ). Переменные, которые требуется обработать этой функции из рабочего пространства getPipeEquations, не особо велики, но еще более сумасшедшим является то, что когда я просматриваю дескриптор функции:

>> f = functions(Network.jacobianFun)
f = 

     function: [1x8323 char]
         type: 'anonymous'
         file: '...\pkg\+adv\+pipe\getPipeEquations.m'
    workspace: {2x1 cell}

... поле рабочей области содержит все, что имело свойство getPipeEquations (что, кстати, не является полным рабочим пространством Matlab).

Если я вместо этого перемещаю оператор eval в подфункцию, пытаясь заставить область, дескриптор будет экономить гораздо компактнее (~ 1 МБ):

function Jv = getJacobianHandle(Jv_str,listConnected,areaPipes,endNodes,D,L,g,dz)
eval(['Jv = @(v,f,rho)', Jv_str, ';']);

Является ли это ожидаемым поведением? Есть ли более элегантный способ ограничить область этой анонимной функции?

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

Ответ 1

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

Здесь минимальное воспроизведение.

function fcn = so_many_variables()
a = 1;
b = 2;
c = 3;
fcn = @(x) a+x;
a = 42;

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

>> f = so_many_variables;
>> f_info = functions(f);
>> f_info.workspace{1}
ans = 
    a: 1
>> f_info.workspace{2}
ans = 
    fcn: @(x)a+x
      a: 1
      b: 2
      c: 3

Это было для меня неожиданностью. Но это имеет смысл, когда вы думаете об этом: из-за наличия feval и eval, Matlab на самом деле не может знать, какие переменные анонимная функция на самом деле собирается связать. Таким образом, он должен захватывать все в области на всякий случай, если они будут динамически ссылаться, как на этом надуманном примере. Это использует значение foo, но Matlab не будет знать этого, пока вы не вызовете дескриптор возвращаемой функции.

function fcn = so_many_variables()
a = 1;
b = 2;
foo = 42;
fcn = @(x) x + eval(['f' 'oo']);

Обходной путь, который вы делаете - выделение функции в отдельной функции с минимальным рабочим пространством - звучит как правильное исправление.

Здесь обобщенный способ получить это ограниченное рабочее пространство для создания вашей анонимной функции.

function eval_with_vars_out = eval_with_vars(eval_with_vars_expr, varargin)

% Assign variables to the local workspace so they can be captured
ewvo__reserved_names = {'varargin','eval_with_vars_out','eval_with_vars_expr','ewvo__reserved_names','ewvo_i'};
for ewvo_i = 2:nargin
    if ismember(inputname(ewvo_i), ewvo__reserved_names)
        error('variable name collision: %s', inputname(ewvo_i));
    end
    eval([ inputname(ewvo_i) ' = varargin{ewvo_i-1};']);
end
clear ewvo_i ewvo__reserved_names varargin;

% And eval the expression in that context
eval_with_vars_out = eval(eval_with_vars_expr);

Длинные имена переменных здесь ухудшают читаемость, но уменьшают вероятность столкновения с переменными вызывающего.

Вы просто вызываете eval_with_vars() вместо eval() и передаете все входные переменные в качестве дополнительных аргументов. Тогда вам не нужно вводить определение статической функции для каждого из ваших анонимных функций. Это будет работать до тех пор, пока вы знаете, на какие переменные на самом деле будут ссылаться, что является тем же ограничением, что и подход с getJacobianHandle.

Jv = eval_with_vars_out(['@(v,f,rho) ' Jv_str],listConnected,areaPipes,endNodes,D,L,g,dz);

Ответ 2

Анонимные функции захватывают все в пределах своей области и сохраняют их в рабочей области функции. См. Документацию MATLAB для анонимных функций

В частности:

"Переменные, указанные в теле выражения. MATLAB захватывает эти переменные и удерживает их постоянными на протяжении всего жизненного цикла дескриптора функции.

Последние переменные должны иметь значение, присвоенное им при создании анонимной функции, которая их использует. При построении MATLAB фиксирует текущее значение для каждой переменной, указанной в теле этой функции. Функция будет продолжать связывать это значение с переменной, даже если значение должно измениться в рабочей области или выйти из области видимости. "

Ответ 3

Альтернативным обходным путем для вашей проблемы является использование того факта, что функция matlab save может использоваться для сохранения только определенных переменных, которые вам нужны. У меня были проблемы с функцией save, сэкономившей слишком много данных (совсем другой контекст от вашего), но некоторые соглашения о присвоении имен и использование подстановочных знаков в списке переменных заставили все мои проблемы уйти.