【问题标题】:Using Array and Table Functions in Mathematica. Which is best when在 Mathematica 中使用数组和表函数。什么时候最好
【发布时间】:2011-08-11 12:12:35
【问题描述】:

我主要是数学中的表函数用户。但是我注意到,在我使用 Array 而不是 Table 来表达相同结果的几个示例中,它的运行速度明显更快,尤其是随着 table 的维度越来越大。

所以我的问题是:当执行速度是主要关注点时,什么时候最适合使用表?

什么解释了这种差异?

我的猜测是,由于数组假设列表中的项目之间存在函数关系,因此它存储它们的效率更高,因此使用的内存更少,从而便于存储和后续处理?

这是怎么回事?

【问题讨论】:

  • @Leonid @Sasha 如果菲尔不介意,我想在这里添加一个相关问题。选择ConstantArray 而不是ArrayTable 的唯一原因仅仅是执行速度吗?自从引入 ConstantArray 以来,我一直想知道它的用途。我的意思是,它甚至不能节省你的打字时间。 ConstantArray[1, 10^7]Table[1, {10^7}] 长,而且填表速度如此之快,以至于差异(当作为一个因素给出时是相当大的)并不是很有趣。至少不适用于符号(因子 2)和整数(10)。然而,对于复合物,它变得有趣 (50)。
  • @belisarius 我的意思是说。
  • @Sjoerd 除了加速之外,ConstantArray 的结果会自动打包为数字常量。对于数字常量,很可能您希望将其打包,因此您必须添加时间来打包以其他方式产生的结果(通过TableArray),再加上方便 - 使用ConstantArray 你没有考虑一下。
  • @Leonid 谢谢,不知道。
  • @Sjoerd 我必须补充一点,ArrayTable(后者仅在一维中)将打包可编译的函数,以及大于 SystemOptions->"CompileOptions" 中相应设置的元素数量(“ArrayCompileLength”和“TableCompileLength”)。因此,打包的区别主要是针对小型列表,除非将上述选项重置为更大的值。但是,IMO,它仍然增加了一些便利性,而且将数字转换为像 Array[1&,{100}] 中的常量纯函数看起来有点不自然,只是为了适应 Array 的语法,它需要一个函数。

标签: arrays wolfram-mathematica


【解决方案1】:

不给出具体的例子,很难正确回答你的问题。

由于 Mathematica 是一个封闭源代码程序,因此无法知道 TableArray 背后的确切实现,除非参与 Mathematica 开发的人员解释过。

这些因素会妨碍您推理在什么情况下应该使用哪种结构。实验是你最好的选择,所以我建议你保留程序的并行版本,一个使用Table,一个使用Array。这样,您将能够看到哪个更快。

更好的是,您可以编写一个依赖于全局变量的包装函数。此函数将根据变量使用TableArray 作为底层实现,因此您可以快速从一个版本切换到另一个版本,而无需对代码进行很多修改。

【讨论】:

  • 谢谢。我正在寻找结构性解释,因此数字示例无法提供我正在寻找的见解。我已编辑问题以使其更清晰。
【解决方案2】:

ArrayTable 相比没有性能优势。它们之间存在差异,这使得一个比另一个更受欢迎。


编辑 有几个人注意到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,从而创建常规数组。表没有这种灵活性。

【讨论】:

  • @Phil 我也扩展了答案。当您以较少的评估量获得结果时,性能会更好。见stackoverflow.com/questions/4721171/…
  • 在某些情况下Array 的速度明显更快,尤其是对于多维数组。例如,考虑b = 1000; c = 1000; Timing@(#[[1, 1]] &[ar = Array[(# + #2) &, {b, c}]]) Timing@(#[[1, 1]] &[ta = Table[(i + j), {i, b}, {j, c}]])。所以速度等价的陈述似乎只适用于线性阵列。或许,对于多维数组Table不能充分利用自动编译(或许不能保证数组确实是矩形的,所以不能打包)——对于上述一个数量级的性能差异我没有其他解释例子。
  • @Leonid 在您的示例中,Array 生成的数组是压缩数组,而 Table 不是:Developer`PackedArrayQ /@ {ar, ta} 给出{True, False}
  • @Alexey 这正是重点。 Array 通常可以打包,因为结果数组已知是矩形的。对于Table,这并不总是正确的,因为迭代器可能依赖于其他迭代器。另请参阅我对@Sjoerd 在ConstantArray 上的问题的评论。
  • @Leonid 请查看我的回答的编辑,这解释了为什么 Table 在您昨天的评论中与 Array 的表现不一样。
【解决方案3】:

您的陈述:

但是我注意到,在我使用 Array 而不是 Table 来表达相同结果的几个示例中,它的运行速度明显更快,尤其是当 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}}]  

【讨论】:

  • @belisarius 对于高维数组,在某些情况下ArrayTable 快​​一个数量级,请参阅我对@Sasha 的回答的评论以获取示例。
  • @Sjoerd 你可以用 ShadowOffset 控制它们
  • @belisarius:我在上面对@sasha 的评论中自己做出了这个观察。 @leonid 然后指出,在更高的维度上,数组比桌子有优势......事实上,我在处理 dim>1 的张量时根据工作做出了这个观察。喜欢剧情!
  • @Phil 我误解了你的说法尤其是随着表格的尺寸变大
  • @belisarius 我知道,请使用它;-)
【解决方案4】:

Array 可能更快的一个原因是它通常可以更好地编译第一个参数。

在 Mathematica 7 中:

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

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

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

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

因此可以推断ArrayTable 应该在同一点编译。

但是让我们尝试一下。我将使用Timo's timeAvg function:

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),因此当达到 CompileLength 时 Array 变得更快。许多功能不是那么有利。如果你只是在函数中将乘法改为除法,结果是有理数而不是整数,那么这种自动编译不会发生,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 在更大维度的数组上更快。

【讨论】:

  • +1 用于自动编译的扩展分析。至于为什么Table对于多维数组比较慢,我认为不是因为Table无法编译,而是因为无法确定生成的数组是矩形的(因为可以创建非矩形嵌套带有Table) 的列表,因此无法打包-正如我在对@Sasha 的回答的第一条评论中提到的那样。而且,如果您不打包,则使用 Compile 几乎没有优势,假设您的函数对单个数字的计算要求并不高。但这只是猜测,我不确定。
【解决方案5】:

Michael Trott 在Programming (pp 707 - 710) 中解决了ArrayTable 之间的差异问题,并认为Table 具有HoldAll 属性,它计算其每次调用的参数,而Array“尽可能”仅在开始时计算其参数。这可能会导致行为和速度的差异。

Attributes[Table]

{全部保留,受保护}

Attributes[Array]

{受保护}

Michael Trott 使用以下示例来说明速度和行为方面的差异。我从他的书(光盘)中逐字记录它们。我希望我这样做不会违反任何规则。

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,空}

{0.102587,空}

(我在 Mac 上使用 mma7。我的 Programming 副本使用 v5.1。可能会有更新)

当然,这不是编程中讨论的ArrayTable 之间的唯一区别。

我查看其他答案,我有兴趣了解其他人对这一点的看法。

【讨论】:

    猜你喜欢
    • 2010-12-12
    • 2012-05-24
    • 1970-01-01
    • 2020-02-27
    • 1970-01-01
    • 1970-01-01
    • 2010-10-28
    • 1970-01-01
    • 2018-07-29
    相关资源
    最近更新 更多