Подтвердить что ты не робот

Состояние, блок, модуль - в каком режиме наибольшая память и эффективность вычислений?

В Mathematica всегда есть несколько способов сделать то же самое. Например, при адаптации решения WReach для моей недавней проблемы я использовал Condition:

ClearAll[ff];
SetAttributes[ff, HoldAllComplete];
ff[expr_] /; (Unset[done]; True) := 
 Internal`WithLocalSettings[Null, done = f[expr], 
  AbortProtect[If[! ValueQ[done], Print["Interrupt!"]]; Unset[done]]]

Однако мы можем сделать то же самое с Block:

ClearAll[ff];
SetAttributes[ff, HoldAllComplete];
ff[expr_] := 
 Block[{done}, 
  Internal`WithLocalSettings[Null, done = f[expr], 
   AbortProtect[If[! ValueQ[done], Print["Interrupt!"]]]]]

Или с помощью Module:

ClearAll[ff];
SetAttributes[ff, HoldAllComplete];
ff[expr_] := 
 Module[{done}, 
  Internal`WithLocalSettings[Null, done = f[expr], 
   AbortProtect[If[! ValueQ[done], Print["Interrupt!"]]]]]

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

4b9b3361

Ответ 1

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

In[14]:= 
Clear[f,fm,fb,fmp]; 
f[x_]:=x;
fm[x_]:=Module[{xl = x},xl];
fb[x_]:=Block[{xl = x},xl];
Module[{xl},fmp[x_]:= xl=x]

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

In[19]:= f/@Range[100000];//Timing
Out[19]= {0.063,Null}

In[20]:= fm/@Range[100000];//Timing
Out[20]= {0.343,Null}

In[21]:= fb/@Range[100000];//Timing
Out[21]= {0.172,Null}

In[22]:= fmp/@Range[100000];//Timing
Out[22]= {0.109,Null} 

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

Для реальных функций и в большинстве случаев накладные расходы либо Module, либо Block не должны иметь значения, поэтому я бы использовал все, что безопаснее (обычно Module). Если это имеет значение, одним из вариантов является использование постоянных локальных переменных, созданных модулем, только один раз. Если даже это накладные расходы значительны, я бы пересмотрел дизайн - с тех пор, очевидно, ваша функция делает слишком мало. Существуют случаи, когда Block более выгодно, например, когда вы хотите быть уверенным, что вся память, используемая локальными переменными будет автоматически выпущена (это особенно актуально для локальных переменных с DownValues, так как они не всегда мусор - собираются при создании Module). Другой причиной использования Block является то, когда вы ожидаете возможности прерываний, таких как исключения или прерывания, и хотите, чтобы локальные переменные автоматически были reset (что делает Block). Однако, используя Block, вы рискуете столкновениями имен, поскольку он связывает переменные динамически, а не лексически.

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

ИЗМЕНИТЬ

Объединив Block и Module в предложенном здесь:

Module[{xl}, fmbp[x_] := Block[{xl = x}, xl]]

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