【问题标题】:Performance tuning in Mathematica?Mathematica 中的性能调优?
【发布时间】:2011-01-18 06:36:45
【问题描述】:

您使用哪些性能调优技巧来使 Mathematica 应用程序更快? MATLAB 有一个很棒的分析器,但据我所知,Mathematica 没有类似的功能。

【问题讨论】:

  • 如果我想接受两个答案怎么办???以前从未发生过这种情况。感谢您的精彩回复!
  • Jon McLoone 题为10 Tips for writing fast Mathematica code的文章
  • @Tobi 虽然 Jon 的大多数建议都非常有效,并且许多与以下答案中的建议重叠,但他建议使用 Block 代替 Module 可能是一种危险的做法,我d 宁愿避免(除了可能在包内,但甚至可能在那里)。对于最小的效率提升,您会遇到变量冲突的危险,这可能是大型项目中的调试噩梦。在包中使用 Block 时情况会好一些,因为名称冲突只会发生在来自同一包(上下文)的符号中。
  • @LeonidShifrin 我不知道,我一定会记住的。

标签: programming performance-tuning


【解决方案1】:

由于 Mathematica 是一个符号系统,因此具有符号评估器更多 一般比在 Matlab 中,性能调整可以是不足为奇的 这里更棘手。技巧有很多,但都可以理解 从一个单一的主要原则。它是:

尽可能避免完整的 Mathematica 符号求值过程。

所有技术似乎都反映了它的某些方面。这里的主要思想是,大多数 有时,一个缓慢的 Mathematica 程序是这样的,因为许多 Mathematica 函数非常通用。这 通用性是一个很大的优势,因为它使语言能够支持更好和更强大的抽象,但是在程序中的许多地方,如果不小心使用这种通用性,可能会(巨大的)过度杀伤力。

我将无法在有限的篇幅中给出许多说明性示例,但可以在 几个地方,包括一些 WRI 技术报告(Daniel Lichtblau 的 想到 Mathematica 中的高效数据结构),一个非常好的 David Wagner 关于 Mathematica 编程的书,最值得注意的是,许多 Mathgroup 帖子。我还在my book 中讨论了其中的一部分。我会尽快提供更多参考资料。

这里有一些最常见的(我只列出了 Mathematica 中可用的那些) 语言本身,更不用说 CUDA \ OpenCL,或其他语言的链接,它们是 当然也有可能):

  1. 一次将尽可能多的工作推入内核,使用尽可能大的工作 一次尽可能多的数据块,而不是将它们分解成碎片

    1.1。尽可能使用内置函数。由于它们已实施 在内核中,在较低级别的语言 (C) 中,它们通常是(但并非总是如此!) 比用户定义的解决同样的问题要快得多。越专业 您能够使用的内置函数的版本,您获得加速的机会就越大。

    1.2。使用函数式编程(Map, Apply 和朋友)。另外,使用纯函数 如果可以,在#-& 表示法中,它们往往比具有命名的 Function-s 更快 论据或基于模式的论据(尤其是对于非计算密集型的 映射到大型列表的函数)。

    1.3。使用结构化和矢量化操作(Transpose, Flatten, Partition, Part 和朋友),它们甚至比函数式运算还要快。

    1.4。避免使用过程式编程(循环等),因为这种编程 风格倾向于将大型结构分解成碎片(数组索引等)。 这会将大部分计算推到内核之外并使其变慢。

  2. 尽可能使用机器精度

    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

  3. 请注意,列表在 Mathematica 中被实现为数组

    3.1。预分配大型列表

    3.2。避免 Append, Prepend, AppendToPrependTo 在循环中,用于构建 列表等(因为它们复制整个列表以添加单个元素,这导致 列表构建的二次而不是线性复杂度)

    3.3。使用链表({1,{2,{3,{}}}} 之类的结构)而不是普通列表 用于程序中的列表累积。典型的成语是a = {new element, a}。 因为 a 是一个引用,所以单个赋值是恒定时间的。

    3.4。请注意,序列模式的模式匹配(BlankSequence, BlankNullSequence) 也基于序列是数组。因此,一个规则 {fst_,rest___}:>{f[fst],g[rest]} 将在应用时复制整个列表。特别是不要 以在其他语言中看起来很自然的方式使用递归。如果要对列表使用递归,请先将列表转换为链表。

  4. 避免低效模式,构建高效模式

    4.1。基于规则的编程可以非常快也可以非常慢,这取决于如何 你建立自己的结构和规则,但在实践中更容易不经意间 让它变慢。强制模式匹配器生成许多规则的规则会很慢 一个修道院注定匹配尝试,例如通过利用不足的每次运行 模式匹配器通过一个长列表(表达式)。对元素进行排序就是一个很好的例子: list//.{left___,x_,middle___,y_,right___}/;x>y:>{left,y,middle,x,right} - 在 列表的大小(解释例如here)。

    4.2。构建有效的模式和相应的结构来存储您的数据,使 模式匹配器以尽可能少地在错误匹配尝试上浪费时间。

    4.3。避免使用具有计算密集型条件或测试的模式。这 当模式主要是语法时,模式匹配器将为您提供最快的速度 性质(测试结构、头部等)。每次条件(/;)或模式测试(?) 使用,对于每个潜在的匹配,评估器由模式匹配器调用, 这会减慢速度。

  5. 注意大多数 Mathematica 内置函数的不可变性质

    大多数处理列表的 Mathematica 内置函数会创建原始列表的副本并 对该副本进行操作。因此,它们可能具有线性时间(和空间)复杂度 原始列表的大小,即使他们只在几个地方修改了列表。一个通用的 内置函数,不创建副本,修改原始表达式并且不 有这个问题,是Part

    5.1。避免将大多数列表修改内置函数用于大量 小的独立列表修改,不能作为一个步骤制定 (例如,NestWhile[Drop[#,1]&,Range[1000],#<500&]

    5.2。使用Part的扩展功能提取和修改大量 同时列出(或更一般的表达式)元素。这个速度非常快 而不仅仅是压缩数字数组(Part 修改了原始列表)。

    5.3。使用Extract一次提取多个不同级别的元素,通过 给它一个可能很大的元素位置列表。

  6. 使用高效的内置数据结构

    以下内部数据结构非常高效,可用于 比他们陈述的主要目的可能出现的情况要多得多。通过搜索 Mathgroup 档案可以找到很多这样的例子,尤其是 Carl Woll 的贡献。

    6.1。打包数组

    6.2。稀疏数组

  7. 使用哈希表。

    从版本 10 开始,Mathematica (Associations) 中提供了不可变关联数组

    7.1 关联

    它们是不可变的这一事实并不能阻止它们有效地插入和删除键值对(通过给定键值对的存在或不存在与原始关联不同的廉价副本)。它们代表了 Mathematica 中惯用的关联数组,具有非常好的性能特点。

    对于早期版本,基于内部 Mathematica 的哈希表,以下替代方案效果很好:

    7.2。基于DownValuesSubValues 的哈希表

    7.3。 Dispatch

  8. 使用元素-位置对偶

    通常您可以编写更快的函数来处理元素的位置,而不是 元素本身,因为位置是整数(对于平面列表)。这可以给你 即使与通用内置函数相比,速度也提高了一个数量级 (以Position 为例)。

  9. 使用收获 - 播种

    ReapSow 提供了一种收集中间结果的有效方法,通常 在计算期间“标记”您想要收集的部分。这些命令也很顺利 使用函数式编程。

  10. 使用缓存、动态编程、惰性求值

    10.1。记忆化在 Mathematica 中很容易实现,并且可以节省大量的执行时间 某些问题的时间。

    10.2。在 Mathematica 中,您可以实现更复杂的记忆化版本,您可以 在运行时定义函数(闭包),这将使用一些预先计算的部分 定义,因此会更快。

    10.3。有些问题可以从惰性求值中受益。这似乎与记忆更相关- 效率,但也会影响运行时效率。 Mathematica 的符号结构使 很容易实现。

一个成功的性能调优过程通常结合使用这些技术, 并且您将需要一些练习来确定每个案例都将是有益的。

【讨论】:

  • Dispatch 的使用我自己怎么强调都不为过!如果可能的话,我经常不遗余力地将ReplaceAll 与调度表一起使用。
  • @Timo:基于 Dispatch 和 DownValues 的哈希有一些不同的特征。为大型键值对列表创建 Dispatched 定义通常比使用 DownValues 更快,并且 Dispatched 规则的应用有时也更快。但是,一旦形成,Dispatched hash 就不容易附加新元素,所以这是一种一次性的事情。使用 DownValues,您就不会遇到这个问题,您可以随时添加更多键值对而不会影响性能。 DownValues 也可以用来实现一种经典的缓存,将 Sort 选项设置为 False
  • @Leonid:一个非常好的答案(+100)!很多有用的提示。您应该在What is in your Mathematica tool bag 问题中添加指向此答案的链接。
  • +1 现在看来,物理学科学家应该也是一个很好的程序员。 :)
  • @TomZinger 很高兴您发现它很有用。回复:10.2:我的意思是记住整个模式。看看this answer 的例子,还有this great answer。还有this one - 在那里,我列出了生成的定义,所以它在你记忆的内容中相当明确。
【解决方案2】:

您可以使用Wolfram Workbench 中包含的分析器

【讨论】:

  • 我看到工作台对高级订阅者是免费的。不幸的是,我们很难与 wolfram 客户服务合作。 :( 我们无法激活任何高级服务。sigh
  • Workbench 中的分析器在哪里?
  • 为了完成交叉引用:也可以在 Mathematica 中进行概要分析。见Profiling from Mathematica
【解决方案3】:

观看 2007 年 Wolfram 技术大会上的演示文稿Principles for Efficient Mathematica Programs

另一个有用的演示文稿是Tips for Memory Efficient Coding in the Wolfram Language

【讨论】:

    【解决方案4】:

    如果我想让我的代码更快,我会检查是否可以使用FunctionCompile 来加快速度:

    https://reference.wolfram.com/language/guide/CodeCompilation.html

    此编译器将 WL 代码转换为 LLVM 字节码,然后可以将其编译为本机机器码。 YouTube上有一个非常好的教程视频,它解释了你可以做什么 用这个编译器做:

    https://www.youtube.com/watch?v=lVdRVUGzJwM

    【讨论】:

    • 太棒了。谢谢@Arnoud
    【解决方案5】:
    猜你喜欢
    • 2011-05-27
    • 1970-01-01
    • 1970-01-01
    • 2013-07-13
    • 1970-01-01
    • 1970-01-01
    • 2015-06-07
    • 2021-07-22
    • 2013-05-26
    相关资源
    最近更新 更多