我对某些优化方法或一般字节码设计感兴趣,这可能有助于使用VM与AST的解释相比,使用VM加速执行。

有帮助吗?

解决方案

AST解释与字节码的主要胜利是操作调度成本,对于高度优化的口译员而言,这开始成为一个真正的问题。 “调度”是用来描述开始执行操作所需的间接费用(例如算术,属性访问等)的术语。

相当普通的基于AST的口译员看起来像这样:

class ASTNode {
    virtual double execute() = 0;
}

class NumberNode {
    virtual double execute() { return m_value; }
    double m_value;
}

class AddNode {
    virtual double execute() { return left->execute() + right->execute(); }
}

因此,执行代码的简单性 1+1 需要3个虚拟呼叫。由于多次拨打电话,Virtual称为非常昂贵的(在宏伟的计划中),以及首先打电话的总费用。

在字节码解释器中,您有一个不同的调度模型 - 而不是虚拟呼叫,而是具有执行循环,类似于:

while (1) {
    switch (op.type) {
        case op_add:
            // Efficient interpreters use "registers" rather than
            // a stack these days, but the example code would be more
            // complicated
            push(pop() + pop());
            continue;
        case op_end:
            return pop();
    }
}

这仍然具有相当昂贵的调度成本与本机代码,但比虚拟调度要快得多。您可以使用称为“计算goto”的GCC扩展名进一步改进PERC,该扩展可以删除开关调度,从而将总调度成本降低到基本上的间接分支。

除了改善调度成本基于字节码的口译员外,比AST解释器还具有许多其他优势,这主要是由于字节码直接“直接”“直接”跳到其他位置作为真实机器的能力,例如,想象一下像这样的代码段落段:

while (1) {
    ...statements...
    if (a)
        break;
    else
        continue;
}

要正确实施此陈述时,您将需要指示执行是要保持在循环还是停止中,因此执行循环变成类似:

while (condition->execute() == true) {
    for (i = 0; i < statements->length(); i++) {
        result = statements[i]->execute();
        if (result.type == BREAK)
            break;
        if (result.type == CONTINUE)
            i = 0;
    }
}

当您添加更多形式的流控制形式时,此信号变得越来越昂贵。一旦添加异常(例如,到处都可能发生的流量控制),您就需要在基本算术的中间检查这些东西,从而导致开销不断增加。如果您想在现实世界中看到这一点,我鼓励您查看eCmascript规范,他们用AST解释器来描述执行模型。

在字节码解释器中,这些问题基本上消失了,因为字节码能够直接表达控制流而不是通过信号传导,例如。 continue 只需将其转换为跳跃指令,只有在实际命中时,您才能获得费用。

最终,根据定义,AST解释器是递归的,因此必须防止溢出系统堆栈,这对您可以在代码中可以重复进行多少限制,例如:类似:

1+(1+(1+(1+(1+(1+(1+(1+1)))))))

口译员有8个层次的递归(至少) - 这可能是非常重要的成本;较旧版本的Safari(前阶段)使用了AST解释器,因此,JS仅允许JS几百级递归,而在现代浏览器中允许1000层。

其他提示

也许您可以查看各种方法 LLVM “ OPT”工具提供。这些是字节码到bytecode的优化,该工具本身将提供有关应用特定优化的好处的分析。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top