Go 作为一种编译型语言,经常用于实现后台服务的开发。由于 Go 初始的开发大佬都是 C 的老牌使用者,因此 Go 中保留了不少 C 的编程习惯和思想,这对 C/C++ 和 PHP 开发者来说非常有吸引力。作为编译型语言的特性,也让 Go 在多协程环境下的性能有不俗的表现。
但脚本语言则几乎都是解释型语言,那么 Go 怎么就和脚本扯上关系了?请读者带着这个疑问,“听” 本文给你娓娓道来~~
什么样的语言可以作为脚本语言?程序员们都知道,高级程序语言从运行原理的角度来说可以分成两种:编译型语言、解释型语言。Go 就是一个典型的编译型语言。
- 编译型语言就是需要使用编译器,在程序运行之前将代码编译成操作系统能够直接识别的机器码文件。运行时,操作系统直接拉起该文件,在 CPU 中直接运行
- 解释型语言则是在代码运行之前,需要先拉起一个解释程序,使用这个程序在运行时就可以根据代码的逻辑执行
汇编语言、C、C++、Objective-C、Go、Rust
JavaScript、PHP、Shell、Python、Lua
Java
PHPJS
可以看到,解释型语言天生适合作为脚本语言,因为它们原本就需要使用运行时来解释和运行代码。将运行时稍作改造或封装,就可以实现一个动态拉起脚本的功能。
但是,程序员们并不信邪,ta们从来就没有放弃把编译型语言变成脚本语言的努力。
为什么需要用 Go 写脚本?首先回答一个问题:为什么我们需要嵌入脚本语言?答案很简单,编译好的程序逻辑已经固定下来了,这个时候,我们需要添加一个能力,能够在运行时调整某些部分的功能逻辑,实现这些功能的灵活配置。
yaegi
1.161.17struct
可以看到,yaegi 的三个优势中,都有 “简” 字。便于上手、便于对接,就是它最大的优势。
快速上手这里,我们写一段最简单的代码,代码的功能是斐波那契数:
const src = ...
fu
从这一点来说就显得非常非常的友好,这意味着运行时,和脚本之间可以直接传递参数,而不需要中间转换。
自定义数据结构传递前文说到,yaegi 的一个极大的优势,是可以直接传递自定义 struct 格式。
这里,我先抛出如何传递自定义数据结构的方法,然后再更进一步讲 yaegi 对第三方库的支持。
比如说,我定义了一个自定义的数据结构,并且希望在 Go 脚本中进行传递:
那么,在对 yaegi 解释器进行初始化的时候,我们可以在 intp 变量初始化完成之后,调用以下代码进行符号表的初始化:
github.com/Andrew-M-C/go.util/sliceRoute
Usegithub.com/A/Bgithub.com/A/BBgithub.com/A/B/BB
Yaegi 支持第三方库
原理
intp.Use(stdlib.Symbols)
stdlib.Symbols
Useyaegi
当然,这种方法只能对脚本所能引用的第三方库进行预先定义,而不支持在脚本中动态加载未定义的第三方库。即便如此,这也极大地扩展了 yaegi 脚本的功能。
符号解析
yaegi
go generate
github.com/Andrew-M-C/go.util/slice
github_com-Andrew-M-C-go_util-slice.go
与其他脚本方案的对比
功能对比
我们在调研了 yaegi 之外,也另外调研和对比了 tengo 和使用 Lua 的 gopher-lua。其中后者也是团队应用得比较成熟的库。
笔者需要特别强调的是:tengo 的标题虽然说自己用的是 Go,但实际上是挂羊头卖狗肉。它使用是自己的一套独立语法,与官方 Go 完全不兼容,甚至乎连相似都称不上。我们应当把它当作另一种脚本语言来看。
这三种方案的对比如下:
总而言之:
- gopher 的优势在于性能
- yaegi 的优势在于 Go 原生语法,以及可以接受的性能
- tengo 的优势?对于笔者的这一使用场景来说,不存在的
但是 yaegi 也有很明显的不足:
0.y.z
性能对比
下文的表格比较多,这里先抛这三个库的对比结论吧:
- 从纯算力性能上看,gopher 拥有压倒性的优势
- yaegi 的性能很稳定,大约是 gopher 的 1/5 ~ 1/4 之间
- 非计算密集型的场景下,tengo 的性能比较糟糕。平均场景也是最差的
简单的 a + b
res := a + b
结果让人大跌眼镜,对于特别简单的脚本,tengo 的耗时极高,很可能是在进入和退出 tengo VM 时,消耗了过多的资源。
而 gopher 则表现出了优异的性能。让人印象非常深刻。
条件判断
该逻辑也很简单,判断输入数是否大于零。测试结果与简单加法类似,如下:
斐波那契数
Fib
这么说吧:tengo 号称与原生 Go 相当,但是实际上整整差了两个数量级,并且还是这几个竞争者之间的性能是最低的。
这个测试结果与 tengo 的 README 上宣称的 benchmark 数据出入也很大,如果读者知道 tengo 的测试方法是什么,或者是我的测试方法哪里有问题,也希望不吝指出~~
工程应用注意要点stdlib.Symbols
os/xxxnet/xxxlogio/xxxdatabase/xxxruntime
此外,虽然 yaegi 直接将脚本函数暴露出来可以直接调用,但是主程序不能对脚本的可靠性做任何的假设。换句话说,脚本可能会 panic,或者是修改了主程序的变量,从而导致主程序 panic。为了避免这一点,我们要将脚本放在一个受限的环境里运行,除了前面通过限制 yaegi 可调用的 package 的方式之外,还应该限制调用脚本的方式。包括但不限于以下几个手段:
recovergosgo
当然,文中充满了对 tengo 的不推崇,也只是在笔者的这种使用场景下,tengo 没有任何优势而已,请读者辩证阅读,也欢迎补充和指正~~
原文标题:《Yaegi,让你用标准 Go 语法开发可热插拔的脚本和插件》
发布日期:2021-10-20