Использование функций Array и Table в Mathematica. Что лучше, когда - PullRequest
22 голосов
/ 23 апреля 2011

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

Поэтому мой вопрос таков: когда скоростьвыполнение является основной задачей, когда наиболее целесообразно использовать таблицу?

Чем объясняется эта разница?

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

Что происходит?

Ответы [ 5 ]

12 голосов
/ 23 апреля 2011

Array не имеет преимуществ по сравнению с Table. Есть различия между ними, которые делают одно предпочтение перед другим.


EDIT Несколько человек отметили, что Table медленнее для многомерных массивов. Все они использовали переменную для хранения размера таблицы. Table имеет HoldAll атрибутов и автоматически оценивает только самые внешние границы. Поскольку внутренние итераторы остаются неоцененными, элемент таблицы не может быть скомпилирован. Использование явных чисел или With с результатом автокомпиляции:
In[2]:= With[{b = 10^4, c = 10^4},
 {Timing@(#[[1, 1]] &[ar = Array[(# + #2) &, {b, c}]]) , 
  Timing@(#[[1, 1]] &[ta = Table[(i + j), {i, b}, {j, c}]])}
 ]

Out[2]= {{4.93, 2}, {4.742, 2}}

In[3]:= Attributes[Table]

Out[3]= {HoldAll, Protected}


Array позволяет вам создать массив значений функций так же, как Table. Они принимают разные аргументы. Array принимает функцию:
In[34]:= Array[Function[{i, j}, a[i, j]], {3, 3}]

Out[34]= {{a[1, 1], a[1, 2], a[1, 3]}, {a[2, 1], a[2, 2], 
  a[2, 3]}, {a[3, 1], a[3, 2], a[3, 3]}}

пока таблица принимает явный вид:

In[35]:= Table[a[i, j], {i, 3}, {j, 3}]

Out[35]= {{a[1, 1], a[1, 2], a[1, 3]}, {a[2, 1], a[2, 2], 
  a[2, 3]}, {a[3, 1], a[3, 2], a[3, 3]}}

Array может работать только с обычными массивами, а Table может выполнять произвольные итерации по списку:

In[36]:= Table[a[i, j], {i, {2, 3, 5, 7, 11}}, {j, {13, 17, 19}}]

Out[36]= {{a[2, 13], a[2, 17], a[2, 19]}, {a[3, 13], a[3, 17], 
  a[3, 19]}, {a[5, 13], a[5, 17], a[5, 19]}, {a[7, 13], a[7, 17], 
  a[7, 19]}, {a[11, 13], a[11, 17], a[11, 19]}}

Иногда Array может быть более кратким. Сравните таблицу умножения:

In[37]:= Array[Times, {5, 5}]

Out[37]= {{1, 2, 3, 4, 5}, {2, 4, 6, 8, 10}, {3, 6, 9, 12, 15}, {4, 8,
   12, 16, 20}, {5, 10, 15, 20, 25}}

против

In[38]:= Table[i j, {i, 5}, {j, 5}]

Out[38]= {{1, 2, 3, 4, 5}, {2, 4, 6, 8, 10}, {3, 6, 9, 12, 15}, {4, 8,
   12, 16, 20}, {5, 10, 15, 20, 25}}

Array позволяет строить выражения с любой головой, а не просто списком:

In[39]:= Array[a, {3, 3}, {1, 1}, h]

Out[39]= h[h[a[1, 1], a[1, 2], a[1, 3]], h[a[2, 1], a[2, 2], a[2, 3]],
  h[a[3, 1], a[3, 2], a[3, 3]]]

По умолчанию заголовок h выбран равным List, что приводит к созданию регулярного массива. Таблица не обладает такой гибкостью.

9 голосов
/ 24 апреля 2011

Майкл Тротт в Программирование (стр. 707 - 710) решает проблему различий между Array и Table и утверждает, что, поскольку Table имеет атрибут HoldAll, он вычисляет свой аргументдля каждого вызова, тогда как Array "по мере возможности" вычисляет свой аргумент только в начале.Это может привести к различиям в поведении и скорости.

Attributes[Table]

{HoldAll, Protected}

Attributes[Array]

{Protected}

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

Remove[a, i, j];
a = 0;
Table[a = a + 1; ToExpression[StringJoin["a" <> ToString[a]]][i, j],
       {i, 3}, {j, 3}]

{{a1 [1, 1], a2 [1, 2], a3 [1, 3]}, {a4 [2,1], a5 [2, 2], a6 [2, 3]}, {a7 [3, 1], a8 [3, 2], a9 [3, 3]}}

a = 0;
Array[a = a + 1; 
 ToExpression[StringJoin["a" <> ToString[a]]], {3, 3}] 

{{a1 [1, 1], a1 [1, 2], a1 [1, 3]}, {a1 [2, 1], a1 [2, 2], a1 [2, 3]}, {a1 [3, 1], a1 [3, 2], a1 [3, 3]}}

(обратите внимание на разницу в поведении)

Для иллюстрации эффекта предварительного вычисления первого аргумента, который он используетследующий пример (снова дословно, стр. 709).

o[a = 0;
  Table[a = a + 1; 
   ToExpression[StringJoin["a" <> ToString[a]]][i, j],
         {i, 3}, {j, 3}], {2000}] // Timing
Do[a = 0;
  Array[a = a + 1; ToExpression[ StringJoin["a" <> ToString[a]]], 
                                            {3, 3}], {2000}] // Timing

{0.700173, Null}

{0.102587, Null}

(я использую mma7 на MacМоя копия Programming использует v5.1. Вполне возможно, что это обновление)

Это не единственная разница между Array и Table, обсуждаемыми в Программирование , конечно.

Я рассматриваю другие ответы, мне будет интересно узнать, что другие думают об этом.

6 голосов
/ 24 апреля 2011

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

В Mathematica 7:

In[1]:= SystemOptions[CompileOptions -> ArrayCompileLength]

Out[1]= {"CompileOptions" -> {"ArrayCompileLength" -> 250}}

и

In[2]:= SystemOptions[CompileOptions -> TableCompileLength]

Out[2]= {"CompileOptions" -> {"TableCompileLength" -> 250}}

Таким образом, можно сделать вывод, что Array и Table должны компилироваться в одной и той же точке.

Но давайте попробуем. Я буду использовать функцию Тимо timeAvg :

n = 15;
Array[Mod[#^2, 5]*(1 + #2) &, {n, n}] // timeAvg
Table[Mod[i^2, 5]*(1 + j), {i, n}, {j, n}] // timeAvg

(* Out = 0.00034496 *)

(* Out = 0.00030016 *)

n = 16;
Array[Mod[#^2, 5]*(1 + #2) &, {n, n}] // timeAvg
Table[Mod[i^2, 5]*(1 + j), {i, n}, {j, n}] // timeAvg

(* Out = 0.000060032 *)

(* Out = 0.0005008   *)

Мы видим, что Array способен компилировать Mod[#^2, 5]*(1 + #2) &, в то время как Table не может компилировать Mod[i^2, 5]*(1 + j) и, следовательно, для Array становится быстрее при достижении CompileLength. Многие функции не так благоприятны. Если вы просто измените умножение на деление в функции, что приведет к рациональному, а не целочисленному результату, такой автокомпиляции не произойдет, и Table будет быстрее:

n = 15;
Array[Mod[#^2, 5]/(1 + #2) &, {n, n}] // timeAvg
Table[Mod[i^2, 5]/(1 + j), {i, n}, {j, n}] // timeAvg

(* Out = 0.000576   *)

(* Out = 0.00042496 *)

n = 16;
Array[Mod[#^2, 5]/(1 + #2) &, {n, n}] // timeAvg
Table[Mod[i^2, 5]/(1 + j), {i, n}, {j, n}] // timeAvg

(* Out = 0.0005744  *)

(* Out = 0.0004352  *)

Но что, если мы тоже сможем сделать эту компиляцию? Если мы используем числа с плавающей запятой, начиная с 1., мы получим Real вывод, который можно скомпилировать:

n = 15;
Array[Mod[#^2, 5]/(1 + #2) &, {n, n}, 1.] // timeAvg
Table[Mod[i^2, 5]/(1 + j), {i, 1., n}, {j, 1., n}] // timeAvg

(* Out = 0.0006256  *)

(* Out = 0.00047488 *)

n = 16;
Array[Mod[#^2, 5]/(1 + #2) &, {n, n}, 1.] // timeAvg
Table[Mod[i^2, 5]/(1 + j), {i, 1., n}, {j, 1., n}] // timeAvg

(* Out = 0.00010528 *)

(* Out = 0.00053472 *)

И снова, Array быстрее в массиве большего размера.

4 голосов
/ 23 апреля 2011

Ваше утверждение:

Однако я заметил, что в нескольких примерах, когда я использовал Array вместо Table для выражения одного и того же результата, он работал заметно быстрее, особенно с ростом размера таблицы.

обычно не верно для одномерных массивов .Взгляните:

Cl;
Needs["PlotLegends`"]
$HistoryLength = 0;
a = 10^8;
arr = {}; tab = {};
Do[(
   arr = {First@Timing@Array[# &, a], arr};
   tab = {First@Timing@Table[i, {i, a}], tab};
   ), {10}];

ListLinePlot[{Flatten@arr, Flatten@tab}, 
 AxesLabel -> {Style["Iteration", 14], Style["Time", 14]}, 
 PlotLegend -> {Style["Array", 14], Style["Table", 14]}, 
 PlotRange -> {{0, 10}, {1.6, 2}}]  

enter image description here

1 голос
/ 23 апреля 2011

Без конкретных примеров трудно ответить на ваш вопрос правильно.

Поскольку Mathematica является программой с закрытым исходным кодом, точные реализации Table и Array не могут быть известны, если тольколюди, которые участвовали в разработке Mathematica.

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

Более того, вы можете написать функцию-обертку, которая зависит от глобальной переменной.Эта функция, в зависимости от переменной, будет использовать Table или Array в качестве базовой реализации, поэтому вы сможете быстро переключаться с одной версии на другую, не делая много изменений в своем коде.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...