Конструктивный подход: перегрузка против коммутатора? - PullRequest
14 голосов
/ 28 ноября 2011

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

  1. … имена функций «перегрузки» (позволяя Mathematica выяснить, какую версию использовать на основе шаблонов / условий / тестов и того, как система упорядочивает определения)?
  2. … или создать отдельную функцию с помощью Switch [] (или аналогичной команды) для прямой оценки?

Выразительность Mathematica часто путает меня с такими глупыми (?) Проблемами, как эта.

Ответы [ 4 ]

11 голосов
/ 28 ноября 2011

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

Я выступаю за то, чтобы принять основную парадигму языка программирования, а не пытаться бороться с ней илинаписать код, который следует идиомам другого языка.Mathematica построена на понятии сопоставления с образцом, поэтому, ИМХО, мы всегда должны сначала рассматривать сопоставление с образцом при попытке выразить себя.Следуя этому принципу, я бы предпочел определения более чем Switch.

. В вопросе производительности меня все больше раздражает растущий акцент на микробенчмарках при сравнении конструкций Mathematica.В то время как полезно знать затраты, связанные с конструкциями, мы должны учитывать Кнута (или это был Хоар?): «Мы должны забыть о малой эффективности, скажем, в 97% случаев: преждевременная оптимизация - корень всего зла».«Зло» - это потеря читабельности в программе, которая в интересах эффективности использует какой-то неясный или косвенный подход для достижения эффекта.Вот мой контрольный список производительности:

  1. Является ли производительность проблемой?Если нет, пропустите остальную часть контрольного списка.

  2. Где узкое место в производительности?Здесь помогает профилировщик, но часто узкое место можно легко найти путем осмотра или нескольких печатных заявлений.Тогда ...

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

  4. Хорошо, алгоритм хорош, поэтому я думаю, что пришло время микробенчмировать.

Я не знаю, достаточно ли амбициозно я использую Mathematica, но большую часть времени я не прохожу шаг 1.И тогда № 3 ловит большую часть остальных.В Mathematica я нахожу, что я просто очень рад, что могу выполнить какую-то амбициозную задачу с небольшим количеством кода - общая производительность обычно не отражается на картине.

О-о, я былучше убери мыльницу.Извините за это.

9 голосов
/ 28 ноября 2011

За исключением очень простых случаев, я предпочитаю использовать функцию с несколькими определениями вместо Switch.Причины этого тройные:

  1. Мне легче читать код, если функция имеет правильное имя.
  2. В случае сбоя / ошибки прощеустановите соответствующее значение по умолчанию и снова вызовите функцию.
  3. Если вы используете функцию, вы можете использовать именованные шаблоны при вычислении результата.

Редактировать

Вот пример, созданный в качестве примера # 2 для Sjoerd:

createNColors[fn_, Automatic, n_] := Table[Hue[i/n], {i, n}]

createNColors[fn_, colors_List, n_] := PadRight[colors, n, colors]

createNColors[fn_, color:(Hue | RGBColor | CMYKColor | GrayLevel)[__], n_] := 
    Table[color, {n}]

createNColors[fn_, color_, n_] := (
    Message[fn::"color", HoldForm[color]]; 
    createNColors[fn, Automatic, n]
    )  

Он может использоваться для генерации набора n цветов для некоторой опции.

9 голосов
/ 28 ноября 2011

Чтобы ответить на вопрос о производительности вашего вопроса, рассмотрите следующие два примера перегрузки и использования Switch[]

switchFunc[a_] :=  Switch[a, _String, 5, _Integer, var, _Symbol, "string"] 

overloadFunc[a_String]  := 5;
overloadFunc[a_Integer] := var;
overloadFunc[a_Symbol]  := "string";

Это чрезвычайно упрощено, но достаточно, чтобы продемонстрировать разницу в производительности

In[1]  := Timing@Nest[switchFunc, x, 1000000]
Out[1] := {3.435, "string"}

In[2]  := Timing@Nest[overloadFunc, x, 1000000]
Out[2] := {0.754, "string"}

Однако, если вы собираетесь перегрузить свою функцию на основе условных тестов, производительность будет хуже, чем Switch[]:

switchFunc2[a_] := Switch[a < 5, True, 6, False, 4];

overloadFunc2[a_ /; a < 5] := 6;
overloadFunc2[a_ /; a > 5] := 4;
overloadFunc2[a_] := a;

In[3]  := Timing@Nest[switchFunc2, 4, 1000000]
Out[3] := {2.63146, 4}

In[4]  := Timing@Nest[overloadFunc2, 6, 1000000]
Out[4] := {4.349, 6}

РЕДАКТИРОВАТЬ: Время в этом ответе было сделано с использованием Mathematica 8.0.1 на OS X 10.7.2. См. Ответ Mr.Wizard для получения дополнительных результатов, где приведенный выше порядок обратный. Тем не менее, я считаю плохой идеей производительности выполнять логические проверки шаблонов аргументов функций.

С точки зрения дизайна, мой личный опыт показывает, что Switch[] и это ужасно, потому что их трудно читать и они медленные. Тем не менее, я также считаю, что выполнение одной и той же функции по-разному в зависимости от типа аргумента, как правило, является плохим дизайном и значительно усложняет отслеживание вашего кода (даже если его легче читать).

6 голосов
/ 28 ноября 2011

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


Для обеспечения практического примененияНапример, я буду использовать это мое решение .Для справки:

f[k_, {}, c__] := If[Plus[c] == k, {{c}}, {}]

f[k_, {x_, r___}, c___] := Join @@ (f[k, {r}, c, #] & /@ Range[0, Min[x, k - Plus[c]]])

Если я переписываю f без сопоставления с образцом и называю это g:

g = Function[{k, L, c},
      If[L === {},
         If[Tr@c == k, {c}, {}],
         Join @@ (g[k, Rest@L, Append[c, #]] & /@ Range[0, Min[First@L, k - Tr@c]])
      ]
    ];

Я чувствую, что это менее понятно, и, конечно, меньшеудобно писать.Мне пришлось использовать явные функции Rest и First, и мне пришлось ввести Append, поскольку я не могу вместить переменное число аргументов.Это также требует использования фиктивного третьего аргумента: {}.

Время показывает, что исходная форма также значительно быстрее:

f[12, {1, 5, 8, 10, 9, 9, 4, 10, 8}]; // Timing
g[12, {1, 5, 8, 10, 9, 9, 4, 10, 8}, {}]; // Timing
{0.951, Null}
{1.576, Null}

InОтвечая на ответ Тимо, я чувствую, что стоит поделиться моими результатами, поскольку они отличаются от его.(Я использую Mathematica 7 на Windows 7.) Кроме того, я полагаю, что он усложнил версию DownValues ​​за пределы функции версии Switch.

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

Array[switchFunc2, 1*^6]; // Timing
Array[overloadFunc2, 1*^6]; // Timing
{1.014, Null}
{0.749, Null}

Так что даже как написано, функция DownValues ​​для меня быстрее.Но второе условие не нужно:

ClearAll[overloadFunc2]

overloadFunc2[a_ /; a < 5] := 6;
overloadFunc2[a_] := 4;

Array[overloadFunc2, 1*^6]; // Timing
{0.546, Null}

Конечно, в случае такой простой функции можно также использовать If:

ifFunc[a_] := If[a < 5, 6, 4]

Array[ifFunc, 1*^6]; // Timing
{0.593, Null}

И если это написано как чистая функция, которую Mathematica компилирует внутри Array:

ClearAll[ifFunc]
ifFunc = If[# < 5, 6, 4] &;

Array[ifFunc, 1*^6]; // Timing
{0.031, Null}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...