【问题标题】:Custom interpreter for mathematical expressions数学表达式的自定义解释器
【发布时间】:2011-07-15 22:51:07
【问题描述】:

我必须评估大量包含变量的表达式,并且我正在考虑编写一个小型自定义解释器以保持编译的快速和小型化。但是我对这个主题没有经验,并且有几个问题。

假设我们有一个包含数学表达式和一组有限对象的文件。该文件可能如下所示:

expr[x,y,z] = 2*x*y + x^2 + 28/14*z*(x*y^2 + 15*z) + ...

我想以某种方式解析它,以便我可以在我的应用程序中对表达式进行数值计算 只需调用函数expr(float x, float y, float z)。参数的数量不应该是固定的(EDIT -: 每个表达式都有自己的定义和适当数量的参数,或者接受一个数组)并且应该允许括号嵌套以保留输入文件相当小。

由于表达式都是多项式类型,我可以想到数据结构应该是什么样子,但是解析看起来很困难。我已经在 SO 上找到了一些类似问题的答案,例如使用Lua。

然而,最大的问题是,与直接从自动生成的 C 代码编译这些表达式相比,创建和调用这些对象时的性能损失是多少。

提前致谢!


EDIT -: 请仅考虑上述expr() 的示例。我想最好的方法是让模板类的对象保存稀疏数组中变量的系数和幂。

【问题讨论】:

  • "调用函数 expr(float x, float y, float z)。参数的数量不应该是固定的" - 那么你有一点问题,因为数字C 或 C++ 函数调用中的参数数量是固定的。即使使用可变参数,被调用者可以处理不同的数字,调用者也必须在编译时修复数字。您可能需要传递一个数组。
  • @Steve Jessop:已解决,我知道这一点。
  • 你为什么不把你的函数表达式写成一个C函数,然后编译/运行呢?

标签: c++ c parsing interpreter


【解决方案1】:

性能有点像字符串长度问题。解释型语言在计算算术表达式时几乎总是比编译的 C 代码慢。但并不是很多程序把大部分时间都花在算术上,所以大部分时间都没有关系。每次评估表达式时都解析表达式还是(从您所说的似乎更有可能)将其解析为某种中间形式,这也会有所不同。

从你所说的内容中无法判断它是否对你很重要,或者你会写多快的解释器,但我不希望它慢 10 倍,就花费的时间而言评估表达式。最初的解释尝试要糟糕得多。

至于中间形式——通常的起点是使用 Dijkstra 的“分流场”算法将中缀表达式转换为反向波兰形式。这为您提供了一系列“符号”、“字节码”,您可以随意称呼它们,并且为该形式编写表达式评估器很容易——每个运算符只需从堆栈中弹出其操作数,执行操作,然后推送将结果压入堆栈,直到表达式的最终值是最后剩下的唯一内容。数字字面量和变量名就像“运算符”,不弹出任何操作数,并推送它们的值。

[编辑 - 根据您的用户是谁,您的程序可能会获取该文本文件,从中生成 C 程序,运行编译器,然后运行生成的程序(或者,打开并调用生成的dll)。显然,这依赖于许多特定于系统的东西(例如,安装了一个编译器),并且需要对表达式进行足够多的评估,以克服编译的开销。]

【讨论】:

  • 感谢您的意见。所以重点是:表达式有时包含数千个术语(最多 15 个参数),应该针对不同的参数值反复评估。解析应该只进行一次。考虑到您从堆栈中推送和弹出的建议,我想我的问题是:如果表达式总是多项式,这是正确的方法吗?如果我尝试在我的第二次编辑中实现这一点,会不会有性能损失(除了初始解析)?
  • @bbrtb:第二次编辑中描述的内容通常比用 C 编写的相同表达式要慢一些。首先,因为访问告诉您幂和系数的稀疏结构有一些开销,其次是因为C 编译器带有出色的优化器。非常简单的例子,如果您的多项式包含重复的子表达式,那么 C 优化器可能会发现它们并避免重复工作,但是您编写的采用一系列系数的评估器将很难做到如此聪明。
  • 我明白了,看起来仍然值得一试。我可以做的在某种程度上优化这个是嵌套多项式对象。我的输入和我的帖子一样,所以它已经被简化了,因为它是用 Mathematica 生成的。
  • @bbrtb:简化与重复使用重复子表达式的中间结果不同。您显示的示例包含x*y 和x*y^2,大概我们会将其转换为C 为x*y*y。所以x*y 可能只计算一次并重复使用,或如果y*y 出现在其他地方,我们不介意忽略浮点乘法并不是真正关联的事实,这可能是而是重复使用。 C 编译器编写者花在思考这些事情上的时间比你或我可能永远都多。
  • 哦,我确定,我真的不想深入这个烂摊子。
【解决方案2】:

您将问题描述为“大型复杂表达式”,并且您担心性能损失。然后你应该考虑编译它们,而不是解释它们。 (根据经验,好的解释器比编译的代码慢 10 倍;糟糕/临时的解释器往往更差)。

通常的途径是以某种方式“编译”表达式,这涉及构建解析器、代码生成器、优化等。

C 编译器已经完成了这一切。所以我认为 将这些表达式翻译成 C 会好得多。 编译它们很容易,与您希望作为解释器做的任何事情相比,执行速度将快如闪电。也可以这样做 使用解析器和更简单的语法定向翻译。

但是如果这些表达式都是由 Mathematica 生成的,那么它们将有一个非常标准但并不复杂的结构。在这种情况下,我猜你可以编写一个基于正则表达式的翻译器,它可以将 Mathematica 形式映射到 C 函数中,而不会有太多麻烦; Perl 将是理想的选择。这为您提供了一个易于实施且非常快速的解决方案。

对于它的价值,我相信 Mathematica 可以选择将 Mathematica 表达式直接转换为 C。似乎这也值得一试。

【讨论】:

  • 感谢您的意见。问题是,我有 100 MB 的表达式要针对不同的参数进行多次评估,所以直接编译不是可行的方法(我已经尝试过这个,我的一个 SO 问题在相关项目中处理了这个问题) .我知道这很难估计(而且我还没有时间研究这个问题),但是 10 倍慢的经验法则是否也适用于我在第二次编辑中提到的评估(在稀疏数组上重复迭代)?
  • 你说直接编译不是要走的路,但除了模糊地引用另一个我显然不知道的项目之外,你没有给出任何更不用说充分的理由。如果你编写一个解释器,你就会有一个解释器。除非你编码得非常好(我觉得这不是你认为自己拥有的特定技能),否则你的表现会比通常的 10 倍差。
  • ...我看了你的其他 SO 问题;最明显相关的是“我的目标文件> 2 Gb”,但您似乎已经解决了这个问题。那么其他解决方案有什么问题呢?我同意大多数回复的语气;我认为你应该重新考虑你是如何获得如此大量如此庞大的功能的。没有人编写所有代码;它以某种方式生成。我认为您需要回到它的生成方式并重新考虑它为什么以这种方式生成;显然,这让问题更难解决。
  • ... 我以构建代码生成器/优化器/编译器为生,在科学计算和代码生成方面有一些经验。我很乐意就此进行离线对话;在我的简历中查看我的电子邮件地址。
  • ...您的稀疏数组方法是一种接近解释器的方法。我不希望这会让您的解释器运行得更快,而且您建议使用稀疏数组(思考点和链接以及存在性测试)这一事实意味着内存访问和条件,这两者都是现代计算机缓慢完成的事情,将出现在您提出的方案中。这些表达式的编译代码不会有这些额外的访问或条件。
【解决方案3】:

Bison Manual 中有一个简单的例子。

【讨论】:

  • 这为 OP 提供了一个解析器,它在解析时评估表达式。如果 OP 必须重新评估表达式,使用此技术将强制 OP 重新解析。在其他评论线程中,他说他不想那样做。
  • 如果 OP 想要将表达式编译成某种对象树,该示例提供了一个起点
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多