Lisp Mathematica中函数和模式匹配的性能差异
因此Mathematica不同于lisp的其他方言,因为它模糊了函数和宏之间的界限。在Mathematica中,如果用户想要编写一个数学函数,他们可能会使用模式匹配,比如Lisp Mathematica中函数和模式匹配的性能差异,lisp,wolfram-mathematica,Lisp,Wolfram Mathematica,因此Mathematica不同于lisp的其他方言,因为它模糊了函数和宏之间的界限。在Mathematica中,如果用户想要编写一个数学函数,他们可能会使用模式匹配,比如f[x]:=x*x而不是f=function[{x},x*x],尽管当使用f[x]调用时,两者都会返回相同的结果。我的理解是,第一种方法相当于lisp宏,根据我的经验,由于语法更简洁,因此更受欢迎 所以我有两个问题,执行函数与模式匹配/宏方法之间是否存在性能差异?不过,如果函数真的被转换成某种版本的宏,以实现Listable等功
f[x]:=x*x
而不是f=function[{x},x*x]
,尽管当使用f[x]
调用时,两者都会返回相同的结果。我的理解是,第一种方法相当于lisp宏,根据我的经验,由于语法更简洁,因此更受欢迎
所以我有两个问题,执行函数与模式匹配/宏方法之间是否存在性能差异?不过,如果函数真的被转换成某种版本的宏,以实现Listable
等功能,我也不会感到惊讶
我之所以关心这个问题,是因为最近有一系列关于在大型程序中捕捉Mathematica错误的问题。如果大多数计算都是根据函数定义的,那么在我看来,跟踪计算顺序和错误产生的位置要比在输入被连续应用宏/模式重写后捕获错误容易得多 模式匹配似乎更快:
In[1]:= g[x_] := x*x
In[2]:= h = Function[{x}, x*x];
In[3]:= Do[h[RandomInteger[100]], {1000000}] // Timing
Out[3]= {1.53927, Null}
In[4]:= Do[g[RandomInteger[100]], {1000000}] // Timing
Out[4]= {1.15919, Null}
模式匹配也更灵活,因为它允许您重载定义:
In[5]:= g[x_] := x * x
In[6]:= g[x_,y_] := x * y
对于简单函数,可以编译以获得最佳性能:
In[7]:= k[x_] = Compile[{x}, x*x]
In[8]:= Do[k[RandomInteger[100]], {100000}] // Timing
Out[8]= {0.083517, Null}
您可以使用前面的函数记录步骤来查看Mathematica对函数的实际操作。它对待它就像对待其他头一样。假设你有以下几点
f = Function[{x}, x + 2];
f[2]
它首先将f[2]转化为
Function[{x}, x + 2][2]
在下一步中,x+2
被转换为2+2
。本质上,“函数”求值的行为类似于模式匹配规则的应用程序,因此它的速度不快也就不足为奇了
您可以将Mathematica中的所有内容都视为一个表达式,其中求值是重写表达式a中部分内容的过程,这适用于函数,就像任何其他头部一样某些测量
根据@gdelfino的回答和@rcollyer的评论,我制作了这个小程序:
j = # # + # # &;
g[x_] := x x + x x ;
h = Function[{x}, x x + x x ];
anon = Table[Timing[Do[ # # + # # &[i], {i, k}]][[1]], {k, 10^5, 10^6, 10^5}];
jj = Table[Timing[Do[ j[i], {i, k}]][[1]], {k, 10^5, 10^6, 10^5}];
gg = Table[Timing[Do[ g[i], {i, k}]][[1]], {k, 10^5, 10^6, 10^5}];
hh = Table[Timing[Do[ h[i], {i, k}]][[1]], {k, 10^5, 10^6, 10^5}];
ListLinePlot[ {anon, jj, gg, hh},
PlotStyle -> {Black, Red, Green, Blue},
PlotRange -> All]
至少对我来说,结果非常令人惊讶:
有什么解释吗?请随意编辑此答案(对于长文本而言,评论是一团糟)
编辑
使用标识函数f[x]=x进行测试,以将解析与实际计算隔离开来。结果(相同颜色):
注:对于常数函数(f[x]:=1),结果与此图非常相似 我对Mathematica的理解是它是一个巨大的搜索引擎。所有函数、变量和其他赋值基本上都存储为规则,在求值过程中,Mathematica将遍历此全局规则库并应用它们,直到生成的表达式停止更改 因此,通过规则列表的次数越少,评估的速度就越快。使用
跟踪查看发生了什么(使用的函数g和h)
很明显,为什么匿名函数最快,为什么使用函数
会比简单的设置延迟
带来额外的开销。我建议看一看Leonid Shifrin的优秀著作,其中详细解释了这些概念
我有时会构建一个包含所有所需函数的表,并手动将其应用于起始表达式。由于Mathematica的内置函数都不需要与我的表达式匹配,因此与正常计算相比,这提供了显著的速度提升
我的理解是,第一种方法相当于lisp宏,根据我的经验,由于语法更简洁,因此更受欢迎
不是真的。Mathematica是一个术语重写器,Lisp宏也是
所以我有两个问题,执行函数与模式匹配/宏方法之间是否存在性能差异
对。请注意,在Mathematica中,您从未真正“执行函数”。您只是应用重写规则将一个表达式更改为另一个表达式
考虑将Sqrt
函数映射到浮点数的压缩数组上。Mathematica中最快的解决方案是将Sqrt
函数直接应用于压缩数组,因为它恰好实现了我们想要的,并且针对这种特殊情况进行了优化:
In[1] := N@Range[100000];
In[2] := Sqrt[xs]; // AbsoluteTiming
Out[2] = {0.0060000, Null}
我们可以定义一个全局重写规则,其术语的形式为sqrt[x]
重写为sqrt[x]
,以便计算平方根:
In[3] := Clear[sqrt];
sqrt[x_] := Sqrt[x];
Map[sqrt, xs]; // AbsoluteTiming
Out[3] = {0.4800007, Null}
注意,这比之前的解决方案慢约100倍
或者,我们可以定义一个全局重写规则,用调用sqrt
的lambda函数替换符号sqrt
:
In[4] := Clear[sqrt];
sqrt = Function[{x}, Sqrt[x]];
Map[sqrt, xs]; // AbsoluteTiming
Out[4] = {0.0500000, Null}
请注意,这比以前的解决方案快约10倍
为什么??因为慢的第二个解决方案是在内部循环中查找重写规则sqrt[x_]:>sqrt[x]
(对于数组的每个元素),而快的第三个解决方案是查找符号sqrt
的值Function[…]
一次,然后重复应用lambda函数。相比之下,最快的第一个解决方案是用C编写的循环调用sqrt
。因此搜索全局重写规则非常昂贵,术语重写也非常昂贵
如果是这样,为什么Sqrt
总是很快?您可能会期望2倍而不是10倍的减速,因为我们已将Sqrt
的一个查找替换为内部循环中Sqrt
和Sqrt
的两个查找,但事实并非如此,因为Sqrt
具有作为内置函数的特殊状态,该函数将在Mathematica术语重写器的核心中匹配本身,而不是通过通用全局重写表
其他人
In[4] := Clear[sqrt];
sqrt = Function[{x}, Sqrt[x]];
Map[sqrt, xs]; // AbsoluteTiming
Out[4] = {0.0500000, Null}
> let xs = [|1.0..100000.0|];;
...
> Array.map sqrt xs;;
Real: 00:00:00.006, CPU: 00:00:00.015, GC gen0: 0, gen1: 0, gen2: 0
...
> open System.Collections.Generic;;
> let fns = Dictionary<string, (obj -> obj)>(dict["sqrt", unbox >> sqrt >> box]);;
> Array.map (fun x -> fns.["sqrt"] (box x)) xs;;
Real: 00:00:00.044, CPU: 00:00:00.031, GC gen0: 0, gen1: 0, gen2: 0
...
> type expr =
| Float of float
| Symbol of string
| Packed of float []
| Apply of expr * expr [];;
> let init n f =
let rec packed ys i =
if i=n then Packed ys else
match f i with
| Float y ->
ys.[i] <- y
packed ys (i+1)
| y ->
Apply(Symbol "List", Array.init n (fun j ->
if j<i then Float ys.[i]
elif j=i then y
else f j))
packed (Array.zeroCreate n) 0;;
val init : int -> (int -> expr) -> expr
> let rec rule = function
| Apply(Symbol "Sqrt", [|Float x|]) ->
Float(sqrt x)
| Apply(Symbol "Map", [|f; Packed xs|]) ->
init xs.Length (fun i -> rule(Apply(f, [|Float xs.[i]|])))
| f -> f;;
val rule : expr -> expr
> rule (Apply(Symbol "Map", [|Symbol "Sqrt"; Packed xs|]));;
Real: 00:00:00.049, CPU: 00:00:00.046, GC gen0: 24, gen1: 0, gen2: 0
| Apply(Symbol "Sqrt", [|Packed xs|]) ->
Packed(Array.map sqrt xs)