方程解析器的效率

Equation parser efficiency

本文关键字:效率 方程解      更新时间:2023-10-16

我花了大约一个月的时间开发一个本地c++方程解析器。它可以工作,除了速度慢(比硬编码方程慢30-100倍)。我能做些什么来加快速度?

我读了所有我能找到的高效代码。粗体:

  • 解析器将字符串方程表达式转换为"操作"对象列表。
  • 操作对象有两个函数指针:一个是getSource,一个是evaluate。
  • 要计算一个方程,我所做的就是在操作列表上执行for循环,依次调用每个函数。

在求值一个等式时不会遇到if/switch——所有的条件都是在解析器最初赋值函数指针时处理的。

  • 我尝试内联函数指针指向的所有函数-没有改进。
  • 从函数指针切换到函子会有帮助吗?
  • 如何删除函数指针框架,而不是创建一套完整的派生"操作"类,每个都有自己的虚拟"getSource"answers"evaluate"函数?(但这不只是将函数指针移动到虚函数表中吗?)

我有很多代码。不确定提炼/张贴什么。你要求它的某些方面,你就会得到它。

在你的文章中你没有提到你已经分析了代码。如果我是你,这是我要做的第一件事。它会给你一个很好的想法,让你知道时间花在哪里,在哪里集中精力进行优化。

从你的描述中很难判断速度慢是包括解析,还是仅仅是解释时间。

解析器,如果您将其写为递归下降(LL1),则应该是I/O受限的。换句话说,解析器读取字符和构造解析树所花费的时间应该比简单地将文件读入缓冲区所花费的时间要少得多。

解释是另一回事。解释代码和编译代码之间的速度差异通常要慢10-100倍,除非基本操作本身很长。也就是说,你仍然可以优化它。

您可以分析,但是在这种简单的情况下,您也可以在调试器中,在单个指令的级别上单步执行程序。这样,你就"站在电脑的角度思考问题",可以很明显地看出哪些地方可以改进。

当我在做你正在做的事情时,也就是说,为用户提供一种语言,但是我希望这种语言有快速的执行,我做的是:我将源语言翻译成我有编译器的语言,然后将其实时编译成.dll(或.exe)并运行它。它非常快,我不需要编写解释器,也不需要担心它有多快。

第一件事是:分析实际出错的地方。瓶颈是在解析还是在求值?Valgrind提供了一些可以帮助你的工具。

如果是在解析中,boost::spirit可能会对您有所帮助。如果在求值中,请记住虚函数的求值速度很慢。我对递归boost::variant有过很好的体验。

你知道,构建表达式递归下降解析器非常容易,表达式的LL(1)语法只有几个规则。解析就变成了一个线性事件,其他的一切都可以在表达式树上工作(基本上是在解析的时候);您将从较低的节点收集数据,并将其传递给较高的节点进行聚合。

这将避免在运行时使用函数/类指针来确定调用路径,而不是依赖已证明的递归性(或者如果您愿意,可以构建迭代的LL解析器)。

似乎你正在使用一个相当复杂的数据结构(据我所知,一个语法树与指针等)。因此,遍历指针解引用在内存方面不是很有效(大量随机访问),可能会显著降低速度。正如Mike Dunlavey所建议的那样,您可以使用另一种语言或通过嵌入编译器(如LLVM)在运行时编译整个表达式。据我所知,microsoft.net通过反射提供了这个特性(动态编译)。Emit和Linq。表达式树。

这是我建议不要使用分析的罕见情况之一。我立即猜测,您正在使用的基本结构是问题的真正根源。在您合理地确定基本结构是合理的之前,对代码进行分析很少有什么价值,并且主要是找出基本结构的哪些部分可以改进。当你真正需要做的是扔掉你拥有的大部分东西,基本上重新开始时,它就不那么有用了。

我建议将输入转换为RPN。要执行此操作,您需要的唯一数据结构是堆栈。基本上,当你到达一个操作数时,你把它压入堆栈。当遇到操作符时,它对堆栈顶部的项进行操作。当对一个格式良好的表达式求值后,堆栈中应该只有一项,即表达式的值。

唯一能提供比这更好性能的方法就是像@Mike Dunlavey建议的那样,生成源代码并通过"真正的"编译器运行它。然而,这是一个相当"沉重"的解决方案。如果你真的需要最大的速度,这显然是最好的解决方案——但如果你只是想提高你现在所做的,转换成RPN并解释它通常会给少量代码带来相当不错的速度提高。