由于 Mathematica 是一个符号系统,因此具有符号评估器更多
一般比在 Matlab 中,性能调整可以是不足为奇的
这里更棘手。技巧有很多,但都可以理解
从一个单一的主要原则。它是:
所有技术似乎都反映了它的某些方面。这里的主要思想是,大多数
有时,一个缓慢的 Mathematica 程序是这样的,因为许多 Mathematica 函数非常通用。这
通用性是一个很大的优势,因为它使语言能够支持更好和更强大的抽象,但是在程序中的许多地方,如果不小心使用这种通用性,可能会(巨大的)过度杀伤力。
我将无法在有限的篇幅中给出许多说明性示例,但可以在
几个地方,包括一些 WRI 技术报告(Daniel Lichtblau 的
想到 Mathematica 中的高效数据结构),一个非常好的
David Wagner 关于 Mathematica 编程的书,最值得注意的是,许多 Mathgroup
帖子。我还在my book 中讨论了其中的一部分。我会尽快提供更多参考资料。
这里有一些最常见的(我只列出了 Mathematica 中可用的那些)
语言本身,更不用说 CUDA \ OpenCL,或其他语言的链接,它们是
当然也有可能):
-
一次将尽可能多的工作推入内核,使用尽可能大的工作
一次尽可能多的数据块,而不是将它们分解成碎片
1.1。尽可能使用内置函数。由于它们已实施
在内核中,在较低级别的语言 (C) 中,它们通常是(但并非总是如此!)
比用户定义的解决同样的问题要快得多。越专业
您能够使用的内置函数的版本,您获得加速的机会就越大。
1.2。使用函数式编程(Map, Apply 和朋友)。另外,使用纯函数
如果可以,在#-& 表示法中,它们往往比具有命名的 Function-s 更快
论据或基于模式的论据(尤其是对于非计算密集型的
映射到大型列表的函数)。
1.3。使用结构化和矢量化操作(Transpose, Flatten,
Partition, Part 和朋友),它们甚至比函数式运算还要快。
1.4。避免使用过程式编程(循环等),因为这种编程
风格倾向于将大型结构分解成碎片(数组索引等)。
这会将大部分计算推到内核之外并使其变慢。
-
尽可能使用机器精度
2.1。注意并使用内置数值函数的 Listability,将它们应用于
大量数据列表,而不是使用Map 或循环。
2.2。尽可能使用Compile。使用Compile的新能力,如CompilationTarget->"C",
并使我们的编译函数并行且可列出。
2.3。尽可能使用矢量化操作(UnitStep, Clip, Sign, Abs 等)
在Compile内部,实现If等“矢量化控制流”构造,使得
您也可以在Compile 内避免显式循环(至少作为最内层循环)。这
在某些情况下,可以将您从 Mathematica 字节码的速度提升到几乎原生的 C 速度。
2.4。使用Compile 时,请确保编译后的函数不会退出非编译评估。查看示例in this MathGroup thread。
-
请注意,列表在 Mathematica 中被实现为数组
3.1。预分配大型列表
3.2。避免 Append, Prepend, AppendTo 和 PrependTo 在循环中,用于构建
列表等(因为它们复制整个列表以添加单个元素,这导致
列表构建的二次而不是线性复杂度)
3.3。使用链表({1,{2,{3,{}}}} 之类的结构)而不是普通列表
用于程序中的列表累积。典型的成语是a = {new element, a}。
因为 a 是一个引用,所以单个赋值是恒定时间的。
3.4。请注意,序列模式的模式匹配(BlankSequence,
BlankNullSequence) 也基于序列是数组。因此,一个规则
{fst_,rest___}:>{f[fst],g[rest]} 将在应用时复制整个列表。特别是不要
以在其他语言中看起来很自然的方式使用递归。如果要对列表使用递归,请先将列表转换为链表。
-
避免低效模式,构建高效模式
4.1。基于规则的编程可以非常快也可以非常慢,这取决于如何
你建立自己的结构和规则,但在实践中更容易不经意间
让它变慢。强制模式匹配器生成许多规则的规则会很慢
一个修道院注定匹配尝试,例如通过利用不足的每次运行
模式匹配器通过一个长列表(表达式)。对元素进行排序就是一个很好的例子:
list//.{left___,x_,middle___,y_,right___}/;x>y:>{left,y,middle,x,right} - 在
列表的大小(解释例如here)。
4.2。构建有效的模式和相应的结构来存储您的数据,使
模式匹配器以尽可能少地在错误匹配尝试上浪费时间。
4.3。避免使用具有计算密集型条件或测试的模式。这
当模式主要是语法时,模式匹配器将为您提供最快的速度
性质(测试结构、头部等)。每次条件(/;)或模式测试(?)
使用,对于每个潜在的匹配,评估器由模式匹配器调用,
这会减慢速度。
-
注意大多数 Mathematica 内置函数的不可变性质
大多数处理列表的 Mathematica 内置函数会创建原始列表的副本并
对该副本进行操作。因此,它们可能具有线性时间(和空间)复杂度
原始列表的大小,即使他们只在几个地方修改了列表。一个通用的
内置函数,不创建副本,修改原始表达式并且不
有这个问题,是Part。
5.1。避免将大多数列表修改内置函数用于大量
小的独立列表修改,不能作为一个步骤制定
(例如,NestWhile[Drop[#,1]&,Range[1000],#<500&])
5.2。使用Part的扩展功能提取和修改大量
同时列出(或更一般的表达式)元素。这个速度非常快
而不仅仅是压缩数字数组(Part 修改了原始列表)。
5.3。使用Extract一次提取多个不同级别的元素,通过
给它一个可能很大的元素位置列表。
-
使用高效的内置数据结构
以下内部数据结构非常高效,可用于
比他们陈述的主要目的可能出现的情况要多得多。通过搜索 Mathgroup 档案可以找到很多这样的例子,尤其是 Carl Woll 的贡献。
6.1。打包数组
6.2。稀疏数组
-
使用哈希表。
从版本 10 开始,Mathematica (Associations) 中提供了不可变关联数组
7.1 关联
它们是不可变的这一事实并不能阻止它们有效地插入和删除键值对(通过给定键值对的存在或不存在与原始关联不同的廉价副本)。它们代表了 Mathematica 中惯用的关联数组,具有非常好的性能特点。
对于早期版本,基于内部 Mathematica 的哈希表,以下替代方案效果很好:
7.2。基于DownValues 或SubValues 的哈希表
7.3。 Dispatch
-
使用元素-位置对偶
通常您可以编写更快的函数来处理元素的位置,而不是
元素本身,因为位置是整数(对于平面列表)。这可以给你
即使与通用内置函数相比,速度也提高了一个数量级
(以Position 为例)。
-
使用收获 - 播种
Reap 和 Sow 提供了一种收集中间结果的有效方法,通常
在计算期间“标记”您想要收集的部分。这些命令也很顺利
使用函数式编程。
-
使用缓存、动态编程、惰性求值
10.1。记忆化在 Mathematica 中很容易实现,并且可以节省大量的执行时间
某些问题的时间。
10.2。在 Mathematica 中,您可以实现更复杂的记忆化版本,您可以
在运行时定义函数(闭包),这将使用一些预先计算的部分
定义,因此会更快。
10.3。有些问题可以从惰性求值中受益。这似乎与记忆更相关-
效率,但也会影响运行时效率。 Mathematica 的符号结构使
很容易实现。