0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于MoonBit的高效符号计算内核Symbit实现方案

OSC开源社区 来源:OSC开源社区 2026-04-24 09:30 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

摘要

本文介绍一个以 MoonBit 实现的符号计算内核 Symbit,目标是通过AI辅助,在保留 sympy 风格符号表达与精确计算能力的同时,将大部分算法移植到 MoonBit,利用 native 与 WebAssembly 后端提升执行效率并降低用户访问门槛,理想情况下用户只需要用浏览器即可访问此计算系统。

我们重点讨论表达式表示的差异化设计,并通过 differential testing 与基准测试评估其正确性与性能。在若干典型 workload 上,该实现相对原始 Python/sympy 路径取得数倍的 native 性能提升,在浏览器上使用 WASM 亦可得到 2x 的平均性能提升,更提高了可访问性。

引言

计算机代数系统(computer algebra system, CAS)是计算机科学和数学领域的重要工具,可以对表达式进行构造、变换、化简与求解。与数值计算不同,符号计算更强调精确性、可解释性与结构保持,操作的对象通常是表达式树。例如,有理数应以精确分数而非浮点近似表示,多项式应保留其代数结构而非退化为采样结果,矩阵运算与方程求解也往往需要在精确域上完成。

在现有开源生态中,sympy 以 Python 为宿主语言,为符号表达、代数变换、微积分、矩阵、方程求解和多项式计算等任务提供了统一而灵活的抽象接口。其优势在于能够与 Python 的科学计算生态自然结合。

然而,这种设计也继承了动态语言运行时的若干工程特征:大量细粒度对象分配、深层递归遍历、频繁的动态分派,以及在表达式重写过程中反复进行的结构构造与析构。在以表达式规范化、多项式操作、精确线性代数为代表的一类高频符号负载中,这些开销会逐步累积,并成为性能瓶颈之一。换言之,符号计算系统的效率问题并不总是来自「数学算法本身不够好」,也常常来自其承载算法的数据表示与运行时执行模型。此外,因为 Python 是动态类型语言,sympy 的实现也很难通过静态分析来保证运行时无类型错误。

除了执行效率,符号计算系统的可部署性同样值得关注。尽管桌面或服务器环境中的 Python 生态已经相当成熟,但当目标场景转向浏览器时,传统方案往往面临更高的接入成本:用户也许会期望「打开网页即可使用」,而不是预先安装解释器、环境或依赖;另一方面,前端交互式教学、在线计算工具和嵌入式数学组件等应用,也要求核心引擎能够以更轻量的方式部署到 Web 环境中。对于此类场景而言,一个既能在 native 环境高效运行、又能以 WebAssembly 形式直接进入浏览器的符号计算内核,具有明确的工程意义。

基于上述动机,我们提出 Symbit,一个用 MoonBit 实现的符号计算内核。其算法设计主要参照 sympy,但在实现层面上,我们重新审视了表达式对象设计、规范化、精确有理数、多项式操作与矩阵消元等核心结构,并通过差分测试验证其正确性。最后我们将在 native 与 WebAssembly 两类环境中评估其性能表现。

设计边界

本节讨论我们的设计的目标与非目标。

语义兼容性的目标

Symbit 的首要目标是在可观测语义上尽可能保持 sympy 风格的符号行为。这里的「可观测语义」至少包括四层:

API 的基本行为:一个用户在构造表达式、调用化简、求解、多项式操作或矩阵运算时,得到的对象种类与主要结果形态应与 sympy 保持一致;

对象语义:例如 Add、Mul、Pow 等核心表达式节点在构造时如何进行扁平化、系数合并、参数保序与未求值保留;

精确数值与集合语义:整数、有理数、有限集合、区间、条件集合等对象必须按照精确代数语义传播,而不能为了简化实现轻易退化为浮点近似或字符串占位丢失信息;

观测语义:打印、展示与前端集成行为,这一层较为灵活,输出字符串可与 sympy 不同,但是语义等价。

工程目标

在语义兼容之外,Symbit 将通过新的实现基座改善执行效率,尤其是 native 场景下的表达式重写、多项式计算、精确矩阵运算与部分求解。当然性能目标并不是抽象的「整体更快」尽管使用 MoonBit 重写确实可以免费获得这一点,但更多的也包含一些算法决策级别的优化。

MoonBit 是多后端编程语言,这带来了一些额外好处:我们可以近乎免费地获得 WebAssembly 部署能力,理论上用户可以直接在浏览器中使用 Symbit,而不需要安装 Python 环境或依赖。这对于在线教学、交互式计算工具和嵌入式数学组件等应用场景具有明显的吸引力。令人惊喜的是,我们最终也完成了这个目标。

非目标

Sympy 有部分包还依赖于 Python 的一些特性,例如 multidispatch 利用 class 机制做多重分派,这在 MoonBit 中并无直接对应,也有依赖于其他复杂 Python 库的包,例如 plotting 依赖于 matplotlib,因此本文并不宣称已经完整迁移 sympy 全部顶层包,而是把目标限定在核心 symbolic engine 相关的包,例如 core、polys、matrices、solvers 等,它们占据了 sympy 90% 的代码。

评估边界

我们采用如下方法评定 Symbit 的完成度:

新增功能必须同时具备局部行为测试与 parity/oracle (差分) 测试,且后者必须覆盖到核心语义边界;

通过静态类型系统把所有类型错误暴露在编译阶段,而不是运行时;

在 native 场景下,Symbit 的算法应该比对应的 sympy 算法有数倍的性能提升。

系统架构

一个符号计算系统的难点在于如何将这些算法如何被组织到统一的对象模型之下,否则就很难称为一个「系统」,而更像是一些独立的工具库。如果表达式表示、精确数值、多项式、矩阵和求解器各自维护一套局部规则,那么系统规模一旦变大,就会出现语义不一致、重复实现、难以测试等问题。因而本章节将讨论 Symbit 的整体架构,以及它与 sympy 在组织方式上的相似与不同。

Sympy 的对象模型

Sympy 的核心是围绕 Basic / Expr 的基于类继承的表达式树。无论是 Add、Mul、Pow 这表表达式基础结构,还是矩阵、集合、关系、函数应用与特殊对象,都需要接入这一套动态对象模型,通过继承来组织表达式,其关系大致是 Add <: Expr <: Basic,在解析 + 的时候会构造一个 Add 对象,Add 的参数又是其他 Expr 对象,依此类推。一旦某个对象被纳入 Basic 体系,它就自动获得了打印、比较、替换、遍历、匹配、重写等一系列通用能力。

classBasic(Printable):
  __slots__ = ('_mhash','_args','_assumptions')

  is_number =False
  is_Atom =False
  is_Symbol =False
  is_Add =False
  is_Mul =False
  is_Pow =False
  ...

@sympify_method_args
classExpr(Basic, EvalfMixin):
  __slots__ = ()
  ...

这让系统在可扩展性和可读性上都非常成功,特别适合逐步长出一个覆盖面极广的 CAS 生态。但它也存在许多不足:很多原本可以在更低层被隔离的成本,最终都会回落到通用对象系统中,例如对象构造开销、动态分派、运行时比较与容器重建。并且因为 Python 的动态类型系统,若写出类型 / 类不匹配的问题时很难在编译阶段发现。

Symbit 的代数数据类型

Symbit 在总体上仍然保留了 sympy 的主题划分,例如 symcore、sympolys、symmatrices、symsolvers、symsimplify 等包大体对应上游的数学模块。其中最底层的是 symcore 与 symnum。前者负责表达式节点、规范化、比较、替换、遍历以及若干基础对象语义,后者则提供精确整数、有理数以及部分数值兼容层 (因此我们也重写了 Python 的任意精度浮点数库 mpmath)。

尽管我们可以用 tagless final 和 trait 来模仿 class 风格的代码组织,但这里我们选择了一种更直接的风格:用一个单一的代数数据类型来表示所有表达式节点,因此 Parser 的代码会相对耦合一些,借助模式匹配和穷尽检查等功能(扩展变体可让类型检查器发现遗漏之处),代码也能保持相当清晰。

pub(all)enumExpr{
 Number(@symnum.BigRational)
 Float(Float)
 NumberSymbol(NumberSymbolKind)
 Boolean(Bool)
 Apply(Expr, Array[Expr])
 Add(Array[Expr])
 Mul(Array[Expr])
 Pow(Expr, Expr)
 Mod(Expr, Expr)
 Tuple(Array[Expr])
 Dict(Array[(Expr, Expr)])
 ...
}
这本质上算是一个表达式问题,但考虑到未来 MoonBit 可能会加入 Extensible Variants 的支持,且数学领域很多东西也是多年不变的,我们认为此种做法或不会受到太多维护成本的影响,并且相比 sympy 在许多方面是更优的。另一方面,sympy 很多表达式处理都是借助节点的「内在属性」,因而有时候需要传递一些额外的参数来维持这些属性,例如evaluate=False来控制是否在构造时进行规范化。
classAdd(Expr, AssocOp):
  ...

classAssocOp(Basic):
  @cacheit
 def__new__(cls, *args, evaluate=None, _sympify=True):
    ...
Symbit 则使用「智能构造子」形式来应对不同场景的规范化需求,可以在一定程度上解耦合(实际上,在编写 Symbit 引擎的过程中我们还发现了 sympy 真实存在的因为参数传递和弱类型导致的 bug):
pubfnexpr_new_raw(op:ExprOp,args:Array[@symcore.Expr])-> @symcore.Expr{
 matchop{
   ExprOp::Add => @symcore.raw_add(args)
    ExprOp::Mul => @symcore.raw_mul(args)
    ExprOp::Pow => @symcore.raw_pow(args[0], args[1])
    ExprOp::Function(name) => @symcore.raw_function(name, args)
    ...
  }
}

pubfnexpr_new_eval(op:ExprOp,args:Array[@symcore.Expr])-> @symcore.Expr{
 matchop{
   ExprOp::Add => @symcore.add(args)
    ExprOp::Mul => @symcore.mul(args)
    ExprOp::Pow => @symcore.pow(args[0], args[1])
    ExprOp::Function(name) => @symcore.function(name, args)
    ...
  }
}


在此之上则是各种代数基础设施包,例如之前提到的 sympolys 和 symmatrices (分别用于多项式处理和矩阵计算)。还有一个 sympy 包,顾名思义,是一个 parity 层,专门用来调用 sympy 进行 oracle 测试。下一节我们将讨论它的设计,以及它在整个系统中的作用。

测试系统

符号系统面对的是结构对象,这些对象往往存在多种语义等价但结构不同的表示形式,并且很多错误并不会立刻表现为程序崩溃,而是表现为规范形不稳定、定义域被忽略、过早求值或错误地返回了一个看似合理的表达式。因此,Symbit 的测试系统从一开始就并不只是依赖于简单的单元测试,而采用一个分层验证体系:用快照测试记录可观察表示,用行为测试约束局部语义,用 parity / oracle 测试对照 sympy,并在必要时直接嵌入 CPython 运行时来复用其参考语义。

更形式化地说,我们希望验证的并不是单个函数的一个输出值,而是一个 MoonBit 实现 f_mb与一个 sympy 参考实现 f_sp在输入域 D 上的观测等价性(当然,D 的表示也因语言不同而存在差异)

4a2e623a-3d74-11f1-90a1-92fbcf53809c.png

其中关系 并不总是简单的字符串相等。在某些场景下,可以是完全结构相等;在另一些场景下,它可能是经规范化后的表达式相等、残差为零(下方公式) 、集合意义下等价,或在浮点物理量场景中满足一定误差界,测试设计者应该根据对象语义选择合适的比较关系。

4a8aeb18-3d74-11f1-90a1-92fbcf53809c.png

MoonBit 的快照式测试

在最基础的一层,Symbit 大量使用 MoonBit 自带的 inspect(..., content=...) 风格测试来记录稳定输出。这种测试主要用来固定一段当前实现的可观察表示,尤其适合打印器、parser、debug representation 以及某些公开 API 的回归验证。与传统手写 assert_eq 相比,这种写法在符号系统里尤其自然,更大的便利是当输出改变时可以直接给出 diff,检查正确后可用 moon test -u 来更新快照,而不需要手动修改断言文本。如果没有成体系的快照更新机制,这类工作往往会退化成机械地逐条修改断言文本,既低效,也容易遗漏。

test"prelude constructors round trip through printers"{
letexpr = add([
 integer(1),
  mul([Symbol("x"), pow(Symbol("y"),integer(2))]),
 ])
 debug_inspect(expr, content="(+ (* sym:x (^ sym:y 2)) 1)")
 inspect(expr, content="x*y**2 + 1")
}

这里的两个 inspect 实际上对应两类不同的断言。前者是内部结构快照(通过 Debug trait 实现),用来保证表达式构造与规范化没有悄悄改变底层表示;后者是用户可见输出快照,用来保证打印行为与 API 接口的稳定性。这两类快照都很重要,因为对于符号系统而言, 「内部结构变化但外部字符串不变」与「外部字符串变化但内部结构不变」都可能导致一些意外的回归问题。

Oracle / Parity Testing 的原理

仅靠快照测试仍然不够。符号系统中更难的问题在于, 「看起来不同」的结果可能是等价的,而「看起来合理」的结果也可能在语义上是错的。因此,Symbit 在包级迁移中采用了第二层测试机制:在 src/sympy/* 下维护一棵镜像式的 parity/oracle 树(谕示机),专门把 MoonBit 的实现结果与 sympy 参考结果进行对照。这一层基本上绑定了 sympy 全部的外部接口。而实现层应永远不依赖 sympy,只有测试层可以调用 sympy 作为参考语义。也就是说,MoonBit 代码能够算出自己的答案,而 oracle 层会将这个答案转换成 sympy 对象,并调用 sympy 对应的 API 来验证它的正确性。或者直接将同样的输入 转换为 sympy 对象并调用 sympy 函数,得到结果后与 symbit 进行比较。

在代码层面上, symbit/src/sym* 负责实现,而 symbit/src/sympy/* 则按包镜像上游模块,包含 *_oracle.mbt、*_port_test.mbt、port_support_test.mbt 等文件。例如 core、polys、solvers、physics 等子树都各自维护了对应的 oracle 层。

在比较关系 R 的设计上,正如我们之前所言,Symbit 并不假设所有对象都适合用同一种比较方式。实践中我们主要使用了三类 parity 技巧。

第一类是直接的结构或文本 oracle。如果一个对象的输出形式本身就是稳定语义的一部分,例如 srepr、latex 或某些调试表示,那么最直接的做法就是把 MoonBit 对象转换成 sympy 对象,再调用 sympy 的对应 printer 取得参考输出,并和 MoonBit 的序列化结果进行字符串比较。例如 core_oracle 中:

pub fn core_expr_sstr(expr :@symcore.Expr) ->Stringraise {
letobj =@sympy.expr_to_sympy(expr)
 py_str_obj(
  objenum_to_obj(
   sympy_call_must("sympy.sstr", [@sympy.OracleArg::PyObj(obj)]),
  ),
 )
}

第二类是经规范化后的表达式等价。对于许多代数结果而言,直接比较字符串会过于脆弱。例如 x - y 与 -y + x 在文本上不同,但在表达式意义下等价。这时更合适的比较关系是:

4ae6f3d6-3d74-11f1-90a1-92fbcf53809c.png

这种比较方式特别适合 simplify、factor、expand、solve 等场景。在求解器中,进一步常见的做法是比较残差而不是比较解的打印形式。如果一个候选解 满足

4b41e110-3d74-11f1-90a1-92fbcf53809c.png

并且其定义域条件与 sympy 一致,那么即便解集的内部排列或局部打印形式不同,它仍然应被视为正确结果。因此在 solver oracle 测试中,我们可以运行最后的结果放置在无序容器中进行比较,而不是试图要求所有结果逐字符一致。

第三类是数值近似语义的 oracle。这类场景主要出现在物理、控制、绘图或某些数值 API 中。在这些路径里,sympy 本身也未必总返回纯精确对象,而 MoonBit 侧有时会经过不同的数值后端或离散化过程。此时更合适的关系通常是

4b982c5a-3d74-11f1-90a1-92fbcf53809c.png

这类比较在系统中并不是主流,但它们提醒我们:oracle 测试的关键不在于「永远追求字符串一致」,而在于为每种对象语义选择恰当的比较关系。

sympy 对象构造

在 parity/oracle 测试里,我们尽量直接通过 FFI 构造 sympy 对象,而不是先把 MoonBit 表达式打印成字符串再交给 Python 解析。如果 oracle 建立在字符串 round-trip 之上,那么 parser、printer 与对象桥接这三类问题就会缠在一起。此时一旦出现不一致,很难判断究竟是 MoonBit 侧对象语义错了,还是打印错了,还是 Python 侧解析路径改变了,非常不利于 debug。

因此,在 pybridge.mbt 中, Symbit 把 Expr 直接映射到 sympy 对象:

pub fn expr_to_sympy(expr :@symcore.Expr) ->@py.PyObject raise {
letdummy_cache : Map[Int,@py.PyObject] = {}
letwild_cache : Map[String,@py.PyObject] = {}
 expr_to_sympy_cached(
 @symcore.normalize_legacy_expr(expr),
  dummy_cache,
  wild_cache,
 )
}
更进一步,Add、Mul、Pow这些对象在桥接时会显式传入evaluate=false,以保留结构语义而不是让 sympy 在桥接阶段重新化简:
@symcore.Expr::Add(args) =>
 sympy_nary_obj("sympy.Add", args,"0", dummy_cache, wild_cache, kwargs={
 "evaluate":OracleArg::Bool(false),
 })
@symcore.Expr::Mul(args) =>
 sympy_nary_obj("sympy.Mul", args,"1", dummy_cache, wild_cache, kwargs={
 "evaluate":OracleArg::Bool(false),
 })
@symcore.Expr::Pow(base, exp) =>
 sympy_call_obj(
 "sympy.Pow",
  [OracleArg::PyObj(base_obj),OracleArg::PyObj(exp_obj)],
  kwargs={"evaluate":OracleArg::Bool(false) },
 )

因为 oracle testing 想比较的是「两个系统对同一对象的理解是否一致」,而不是「MoonBit 打印出的字符串能否再次被 sympy 接受并重写成某种形态」。通过对象级桥接,oracle 层可以最大程度剥离 parser/printer 噪声,把测试焦点放回真正的符号语义上。

利用 CFFI 接入 CPython 运行时的经验

要让上述 parity/oracle 机制可用,还需要一个现实前提: MoonBit 必须能够稳定地嵌入 CPython 运行时,并以足够低的摩擦调用 sympy。在 Symbit 中,这部分实现集中在 symbit/src/sympy 这层 Python bridge。从代码组织上看,它又可以拆成三小层:

types.mbt:定义跨边界参数类型 OracleArg

py_pack.mbt:负责参数打包与 Python 对象构造

py_call.mbt / pybridge.mbt:负责运行时初始化、调用和对象级桥接

例如 types.mbt 中,所有跨边界参数都先被封装成一个统一的代数数据类型:

pub(all)enumOracleArg{
 Null
 Str(String)
 Int(Int)
 Bool(Bool)
 StrList(Array[String])
 IntList(Array[Int])
 List(Array[OracleArg])
 Dict(Map[String, OracleArg])
 PyObj(@py.PyObject)
}

有了这些方便的函数我们就不必再到处手写不同的 FFI 调用,而是通过一层统一的参数打包协议。绝大多数 oracle helper 都可以在 OracleArg 这一层复用相同的参数传递逻辑,而不用每个包都自己拼接 Python 参数。

随后在 py_pack.mbt 中,这些参数被打包成真实的 Python 对象:

pubfnpy_pack(arg:OracleArg)-> @py.PyObject{
matcharg{
 OracleArg::Str(s) => @py.PyString::from(s).obj()
  OracleArg::Int(n) => @py.PyInteger::from(n.to_int64()).obj()
  OracleArg::Bool(b) => @py.PyBool::from(b).obj()
  OracleArg::PyObj(obj) => {
   @cpython.py_incref(obj.obj_ref())
   obj
  }
  ...
 }
}

一个常见的坑时引用计数问题,但跨边界传入现有的 PyObj 时,必须显式 incref,否则一些的生命周期错误会非常难查(它们甚至看起来是随机发生的)。

oracle 层尽量不依赖「把字符串塞进 Python 的 eval 函数」,这对代码可读性、错误定位和测试稳定性都非常不利。我们仅在 core_oracle.mbt 等受限场景下调用 Python eval,但这类逻辑主要用于便捷地获取参考值;而真正从 MoonBit 对象进入 sympy 对象的主路径,仍然是前面提到的 expr_to_sympy。这种分工可以概括为:

4bf627b0-3d74-11f1-90a1-92fbcf53809c.jpg

而不应该是

4c586e7a-3d74-11f1-90a1-92fbcf53809c.png

后者虽然实现更快(且我们早期确实希望通过这个简化实现,但带来的收益不如带来的麻烦多),但它把 parser、printer、引用环境和语义比较混成了一条链,对于迁移工程来说噪声过大。

最后,CFFI 接入还有一个经验是:桥接层必须被视为「测试基础设施」,而不是「核心实现的一部分」。实现层不能够依赖 sympy_call(...) 这类接口来获得最终结果,否则整个迁移项目就会失去意义。

评估

到目前为止,本文讨论了系统目标、对象架构、一个代表性多项式算法以及测试与 oracle 机制。最后一个问题自然是:这些设计在实际运行中带来了什么结果。对于一个 CAS 实现而言,评估不能只给出若干「更快」的例子,因为符号系统的性能高度依赖负载的结构:有些路径主要受精确算术支配,有些受表达式重写支配,有些则受数据结构构造、项排序与规范化频率支配。因此我们应该给不同的包,挑选大部分具有代表性的 API 进行测试,实践中我们借助 symbench 把负载按包与 API 家族组织起来,分别给出 native 与 WebAssembly 路径上的结果。

实际上这里还有个相当有趣的问题就是构造「有趣」的数学表达式以用于测试,这并非一个显然的问题,我们使用了很多 property based testing 中的生成器设计方法来完成此事,对此感兴趣的读者可以见我们之前发表的关于 QuickCheck 的文章。

评估方法

Symbit 的 benchmark 系统建立在 symbench 之上。每个 benchmark case 都包含三部分:MoonBit 侧输入构造器、sympy 侧 oracle 定义,以及统一的运行入口。在进入计时之前,所有 case 都必须先通过语义校验,也就是先确认

4cb18b22-3d74-11f1-90a1-92fbcf53809c.png

再把同一 workload 交给基准脚本计时。无论何时我们都应该先验证语义正确性,再比较性能,毕竟更快地得到错误结果并没有什么意义。

考虑到 Python 具有启动成本,benchmark 为尽可能聚焦算法内核本身的运行代价,应该启动之后多次重复执行同一样例来摊薄启动成本。至于 Symbit 则使用 native-binary + release 路径,在这一定义下,本文采用如下速度比:

4d0f27c8-3d74-11f1-90a1-92fbcf53809c.png

若该值大于 ,则表示 Symbit 更快。本文收集了几种常见这些报表按负载家族分别整理,例如 sympolys-all、symsolvers-all、symseries-all 等,基本上也能对应到 symbit / sympy 的包结构。

基准测试结果

Native 路径上的结果相当不错,在当前已经整理成稳定分包报表的 workload 上,Symbit 在多数包内都取得了稳定领先,且这种领先并不局限于某一种类型的算法。在多项式、求解器、整数论、级数、集合运算、积分以及部分组合函数 workload,都表现出了明显的优势。

下表汇总了若干当前已经稳定维护的分包报表。其中「范围」表示该包内最慢与最快样例的加速比区间,而「代表 case」则列出若干最能体现该类 workload 特征的样例。

4d667d48-3d74-11f1-90a1-92fbcf53809c.png

下图给出了 symseries 分包报表中的加速比分布,可以直观看到不同多项式工作负载之间的差异。其中横轴是具体 benchmark 样例,纵轴是相对于 sympy 的加速比。

4dc0264a-3d74-11f1-90a1-92fbcf53809c.png

可见不同的负载均得到了提升,而不是某一个特例。更多结果表明,在 sympolys 中,gcd、resultant、discriminant、sturm sequence、Groebner basis 与有理插值都明显快于 sympy;在 symsolvers 中,线性方程组、非线性方程组、solveset 与线性规划也都表现出稳定优势。这说明速度提升并不是来自某一个特殊路径,而是来自对象表示、规范化、精确域算术与底层算法组织方式的共同变化。在结构更复杂、对象层成本更突出的 case 上,提升还会继续扩大。当然,在一些已经相对紧凑的组合或微积分核上,结果也可能只领先 1.x 到 3.x。我们也对 WebAssembly 平台进行了测试(对不 sympy 本地解释),也有 1.x ~ 3.x 的整体提升,不过幅度并未有 native 这么大,它更大的优势是浏览器部署,并且输出小巧。

结果解读与局限

这些评估结果支持了本文的基本判断:把 sympy 风格的符号对象系统迁移到 MoonBit,并改变对象表示、规范化链路与底层精确算术的执行特性,确实带来了可观的性能提升。在一批经过 oracle 校验的代表性负载上,这种变化最终表现为稳定的 native 优势,以及仍具竞争力的 WebAssembly 路径。

但与此同时,本文并不将这些数字解释为「所有符号负载都会自动得到同等提升」。首先,当前报表虽然已经覆盖了多项式、求解器、整数论、积分、集合、级数、函数与部分微积分,但它们仍然是经过筛选和构造的 kernel family,而不是完整地覆盖整个 sympy API 空间。其次,benchmark 结果本身也是系统演化过程的一部分。某些样例在项目早期曾经显著落后,后来通过对齐 sympy 代码路径、调整对象表示或改进 exact kernel 而被拉回;这意味着评估系统同时也是性能诊断系统,而不仅是展示系统。若要把它推广为一个更完整的学术评估,还需要在未来补充更多内容:例如更系统的 workload 分类、更大规模的随机与性质测试集、更细粒度的 profiler 分析,以及跨版本的 longitudinal benchmark。

综合来看,Symbit 的评估结果并不是「证明一切都已经完成」,而是表明这条技术路线是成立的:一个保持 sympy 风格语义与精确计算能力的符号系统,确实可以在编译型语言中获得显著的内核运行收益,并同时保留进入浏览器环境的现实可行性。

浏览器 Demo

本项目还利用 MoonBit 的多后端能力打造了一个 Symbit Web-Demo,名为 symweb:https://caimeo.space/symweb

我们将计算内核编译到 WASM 加速,而前端 GUI 部分则使用 Rabbita 编译到 JavaScript。两者之间通过一层很薄的 JSON bridge 通讯。另外借助 KaTeX 来渲染 LaTeX 输出,整个系统在浏览器端形成了一个小型的 CAS 交互界面。 sympy 的经典用法往往需要安装 Python 环境、配置依赖、编写脚本或 notebook,而我们通过 Symweb 把这些交互直接搬到了浏览器里,轻量用户可以直接在页面里编辑表达式、切换 parser 选项、观察结构化结果,并即时得到反馈。我们在 Demo 里面实现了大部分常用的 symbolic API,例如 simplify、expand、factor、solve、series 等,并且在一些页面里还展示了表达式的结构化表示、LaTeX 输出以及求值结果等多层信息。并且页面相当小,整个前端代码和内核编译之后不到 3 MB,相比之下若我们想要把 sympy 运行在浏览器上,需要打包 Pyodide 运行时、自己单独写胶水代码和 std 支持,然后加载 sympy 和 mpmath 本体,肯定不会这么简洁 (运行性能更不用说了)。

4e2659f6-3d74-11f1-90a1-92fbcf53809c.png

Symbit Playground - caimeo.space/symweb

当然,symweb 仍然只是一个 demo,而不是完整的 notebook 或 CAS IDE。它目前更适合展示一个函数如何解析输入、如何形成结构化结果、以及在当前 kernel 中给出什么输出,而不是承担长会话、多文档、任意插件或大规模可视化等更重的任务。对于这种复杂任务,我们建议直接本地安装 Symbit 体验。

未来工作

虽然我们已经取得了许多进展,但未来仍有很多工作需要完成。除了继续部分关键包的语义,减少与 Sympy 在语义上的差距。并逐步推进一些更复杂的算法实现和 Bug 修复:即使是 Sympy 本身也有数千个 issues 待修复和 PR 等待合并,我们希望 Symbit 能一定程度上跟随这些变化和改进,甚至走在前面。

结论

本文致此完结,我们讨论了 Sympy 风格的符号表达、精确计算与常见 CAS 算法,并不必然依赖 Python 这一宿主环境。通过 MoonBit 这样的编译型多后端语言,可以把同样的语义重建为一个能够同时面向 native 与 WebAssembly 的符号计算内核;在这一过程中,表达式表示、规范化、精确数值、多项式与矩阵算法、测试基础设施与部署方式都会被重新组织。当前结果表明,这条路线不仅在工程上可行,而且在若干典型 workload 上已经能够提供稳定的性能收益,并形成一个真实可交互的浏览器前端。这并不意味着 Symbit 已经完成,但至少说明:构建一个编译型、可部署、且保持精确语义的现代 CAS 内核,是一条成立且值得继续推进的方向。

若读者觉得这个项目有趣,欢迎访问 https://github.com/CAIMEOX/symbit.git 以了解更多细节。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    4

    文章

    1476

    浏览量

    43089
  • 计算机
    +关注

    关注

    19

    文章

    7840

    浏览量

    93459
  • AI
    AI
    +关注

    关注

    91

    文章

    41071

    浏览量

    302573
  • python
    +关注

    关注

    58

    文章

    4885

    浏览量

    90301

原文标题:用 MoonBit 构建现代符号计算内核:Symbit 的设计与实现

文章出处:【微信号:OSC开源社区,微信公众号:OSC开源社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    LMV7219电特性表里面的失调电压和内部滞环的计算是带符号计算吗?

    您好,LMV7219电特性表里面的失调电压和内部滞环的计算是带符号计算吗?按照下图电特性表(4)的定义:内部滞环的典型值为3.5mV-(-4mV)=7.5mV,而失调电压等于(3.5mV+(-4mV))/ 2=0.25mV ?
    发表于 07-31 06:41

    【「AI芯片:科技探索与AGI愿景」阅读体验】+AI的未来:提升算力还是智力

    不同 ②学习目标不同 ③降低功耗不同 四、符号计算 1、超维计算 超维计算基本要素: ①高维空间②准正交性③相似度测量④种子超向量⑤条目存储器 超维计算的运算方式与复合表示: 超维
    发表于 09-14 14:04

    科学计算与matlab语言教程下载

    科学计算与matlab语言教程下载 介绍matlab概述与运算基础介绍matlab程序设计matlab文件操作绘图形功能线形代数中的数值计算问题数据处理方法与多项式matlab符号计算matlab的图形用户界面设计[此
    发表于 06-18 14:11

    鲜大权《西南科技大学MATLAB教学ppt课件》

    `参考目录第1讲绪论第2讲MATLAB概述第3讲MATLAB数组与运算第4讲 MATLAB数值计算一第5讲 MATLAB数值计算二第6讲MATLAB符号计算一第7讲MATLAB符号计算
    发表于 07-14 11:02

    高效学习Linux内核

    高效学习Linux内核
    发表于 08-19 23:43

    Linux内核符号有哪些

    Linux内核符号
    发表于 05-29 15:46

    高效学习Linux内核——内核模块编译

    内核是世界上最大的开源项目之一,但是内核是什么,它用于什么?一、什么是linux内核模块?内核是与计算机硬件接口的易替换软件的最低级别。它负
    发表于 09-24 09:11

    matlab与科学计算下载

    matlab与科学计算 介绍matlab概述与运算基础介绍matlab程序设计matlab文件操作绘图形功能线形代数中的数值计算问题 数据处理方法与多项式matlab符号计算
    发表于 06-18 14:16 39次下载

    用Maple和MATLAB解决科学计算问题

    本书的作者非常出色地将科学计算问题与两个著名的数学软件包—Maple和MATLAB*,联系在一起。主要的原因是:Maple是符号计算领域里能力最强的软件包,而MATLAB*在数值和工程计算领域
    发表于 06-19 14:32 56次下载

    符号计算系统Mathematica电子教案

    发表于 04-13 08:12 0次下载

    matlab主要功能

    在数学、应用科学和工程计算领域,常常会遇到符号计算的问题。MATLAB通过收购MAPLE的使用权,实现符号计算功能。
    的头像 发表于 03-27 17:08 2.3w次阅读

    SymPy: 符号计算库是什么

    SymPy 是一个用 Python 编写的符号计算库,它可以用来进行符号计算,包括初等数学和高等数学,甚至研究生数学的符号计算
    的头像 发表于 03-03 14:31 2847次阅读
    SymPy: <b class='flag-5'>符号计算</b>库是什么

    MATLAB符号计算和代数运算

    当涉及到MATLAB符号计算和代数运算时,有许多不同的功能可供使用。
    的头像 发表于 07-07 09:28 2016次阅读

    一个关于MATLAB极限的实验介绍和总结示例

    实验目的:通过使用 MATLAB 计算极限,加深对极限概念的理解,并熟悉 MATLAB 中的符号计算工具箱。
    的头像 发表于 07-17 10:18 2060次阅读

    开源编程语言MoonBit 2024年度技术盘点

    2024年对 MoonBit 来说是一个非凡的里程碑年。在 AI 浪潮澎湃的一年里,用户数量呈现指数级增长,新功能陆续发布,会议演讲创下新纪录,社区也形成生态规模。随着年终将至,回顾这一
    的头像 发表于 01-16 09:18 2704次阅读
    开源编程语言<b class='flag-5'>MoonBit</b> 2024年度技术盘点