我正在研究一种中间语言和一个虚拟机来运行具有几个“有问题”属性的函数式语言:
- 词法命名空间(闭包)
- 动态增长的调用堆栈
- 慢整数类型(bignums)
中间语言是基于堆栈的,具有当前命名空间的简单哈希表。这样就可以了解它的外观 McCarthy91 功能:
# McCarthy 91: M(n) = n - 10 if n > 100 else M(M(n + 11))
.sub M
args
sto n
rcl n
float 100
gt
.if
.sub
rcl n
float 10
sub
.end
.sub
rcl n
float 11
add
list 1
rcl M
call-fast
list 1
rcl M
tail
.end
call-fast
.end
“大循环”很简单:
- 获取指令
- 递增指令指针(或 程序计数器)
- 评估指令
随着 sto
, rcl
还有更多,函数调用有三个指令:
call
复制命名空间(深层复制)并将指令指针推送到调用堆栈call-fast
是相同的,但只创建一个浅的副本tail
基本上是'转到''
实施非常简单。为了给你一个更好的主意,这里只是一个来自“大循环”中间的随机片段(更新,见下文)
} else if inst == 2 /* STO */ {
local[data] = stack[len(stack) - 1]
if code[ip + 1][0] != 3 {
stack = stack[:len(stack) - 1]
} else {
ip++
}
} else if inst == 3 /* RCL */ {
stack = append(stack, local[data])
} else if inst == 12 /* .END */ {
outer = outer[:len(outer) - 1]
ip = calls[len(calls) - 1]
calls = calls[:len(calls) - 1]
} else if inst == 20 /* CALL */ {
calls = append(calls, ip)
cp := make(Local, len(local))
copy(cp, local)
outer = append(outer, &cp)
x := stack[len(stack) - 1]
stack = stack[:len(stack) - 1]
ip = x.(int)
} else if inst == 21 /* TAIL */ {
x := stack[len(stack) - 1]
stack = stack[:len(stack) - 1]
ip = x.(int)
问题是这样:调用McCarthy91 16次,值为-10000,接近没有差别,3秒(在优化掉深拷贝之后,增加了近一秒)。
我的问题是:优化这种语言的解释有哪些常用技巧?有没有悬挂水果?
我使用切片作为我的列表(参数,各种堆栈,命名空间的映射片段......),所以我在这个地方做了这样的事情: call_stack[:len(call_stack) - 1]
。现在,我真的不知道哪些代码会使这个程序变慢。任何提示将不胜感激,但我主要是寻找一般的优化策略。
在旁边:
通过规避我的调用约定,我可以减少执行时间。该 list <n>
指令获取堆栈的n个参数并将它们的列表推回到堆栈中 args
指令弹出该列表并将每个项目推回到堆栈中。这首先要检查是否使用正确数量的参数调用函数,其次是能够使用可变参数列表调用函数(即 (defun f x:xs)
)。删除它,并添加指令 sto* <x>
,取代 sto <x>; rcl <x>
,我可以把它降到2秒。仍然没有辉煌,我必须有这个 list
/args
无论如何,生意。 :)
另外一个(这是一个很长的问题,我知道,抱歉):
用pprof对程序进行分析很少告诉我(如果不是很明显,我是Go的新手):-)。这些是pprof报告的前3项:
16 6.1% 6.1% 16 6.1% sweep pkg/runtime/mgc0.c:745
9 3.4% 9.5% 9 3.4% fmt.(*fmt).fmt_qc pkg/fmt/format.go:323
4 1.5% 13.0% 4 1.5% fmt.(*fmt).integer pkg/fmt/format.go:248
这些是我到目前为止所做的改变:
- 我删除了哈希表。相反,我现在只传递指向数组的指针,而且只在必要时才有效地复制本地范围。
- 我用整数操作码替换了指令名。之前,我浪费了相当多的时间比较字符串。
- 该
call-fast
指令消失了(在其他变化之后,加速不再可测量) - 我没有“int”,“float”和“str”指令,而只有一个
eval
我在编译时评估常量(编译字节码)。然后eval
只是推动对它们的引用。 - 改变后的语义
.if
,我可以摆脱这些伪函数。下雪了.if
,.else
和.endif
,隐含的getos和块语义类似于.sub
。 (一些示例代码)
在实现词法分析器,解析器和字节码编译器之后,速度有点下降,但并非如此。计算MC(-10000)16次使其在1.2秒内评估420万字节码指令。这里的 它生成的代码示例 (从 这个)。