两周自制脚本语言-第11天 优化变量读写性能

第11天 优化变量读写性能

以变量值的读写为例,向读者介绍基于这种理念的语言处理器性能优化方式。

11.1 通过简单数组来实现环境

假如函数包含局部变量x与y,程序可以事先将x设为数组的第0个元素,将y设为第1个元素,以此类推。这样一来,语言处理器引用变量时就无需计算哈希值。也就是说,这是一个通过编号,而非名称来查找变量值的环境

为了实现这种设计,语言处理器需要在函数定义完成后遍历对应的抽象语法树节点,获取该节点使用的所有函数参数与局部变量。遍历之后程序将得到函数中用到的参数与局部变量的数量,于是确定了用于保存这些变量的数组的长度

之后,语言处理器在实际调用函数,对变量的值进行读写操作时,将会直接引用数组中的元素。变量引用无需再像之前那样通过在哈希表中查找变量名的方式实现。

确定变量的值在数组中的保存位置之后,这些信息将被记录于抽象语法树节点对象的字段中。例如,程序中出现的变量名在抽象语法树中以Name对象表示。这一Name对象将事先在字段中保存数组元素的下标,这样语言处理器在需要引用该变量时,就能知道应该引用数组中的哪一个元素。Name对象的eval方法将通过该字段来引用数组元素,获得变量的值。

不必在程序执行时通过变量名来查找变量。

如果希望在Name对象的字段中保存变量的引用,仅凭数组元素仍然不够,还需要同时记录与环境对应的作用域。环境将以嵌套结构实现闭包。为此,Environment对象需要通过outer字段串连。此外,Name对象还要记录环境所处的层数,即从最内层向外数起,当前环境在这一连串Environment对象中的排序位置。该信息保存于Name对象的nest字段中。index字段则用于记录变量的值在与nest字段指向的环境对应的数组中,具体的保存位置。

图11.1 x=2的抽象语法树与环境

下图是表示x=2的抽象语法树。在该图中,变量x的值保存于从最内层数起的第2个环境对应的数组中,因此Name对象的nest字段的值为1(如果是最内层,则值为0)。由于变量x的值保存于该数组的第3个元素中,因此index字段的值为2

代码清单11.1 ArrayEnv.java

为了实现一个通过数组来保存变量值的环境,需要新定义一个ArrayEnv类。提供了与NestedEnv类几乎相同的功能,实现了Environment接口。ArrayEnv类没有使用哈希表,仅通过简单的数组实现了变量值的保存。

package chap11;
import stone.StoneException;
import chap11.EnvOptimizer.EnvEx2;
import chap6.Environment;

public class ArrayEnv implements Environment {
    protected Object[] values;
    protected Environment outer;

    public ArrayEnv(int size, Environment out) {
        values = new Object[size];
        outer = out;
    }

    public Symbols symbols() {
        throw new StoneException("no symbols");
    }

    public Object get(int nest, int index) {
        if (nest == 0)
            return values[index];
        else if (outer == null)
            return null;
        else
            return ((EnvEx2) outer).get(nest - 1, index);
    }

    public void put(int nest, int index, Object value) {
        if (nest == 0)
            values[index] = value;
        else if (outer == null)
            throw new StoneException("no outer environment");
        else
            ((EnvEx2) outer).put(nest - 1, index, value);
    }

    public Object get(String name) {
        error(name);
        return null;
    }

    public void put(String name, Object value) {
        error(name);
    }

    public void putNew(String name, Object value) {
        error(name);
    }

    public Environment where(String name) {
        error(name);
        return null;
    }

    public void setOuter(Environment e) {
        outer = e;
    }

    private void error(String name) {
        throw new StoneException("cannot access by name: " + name);
    }
}

11.2 用于记录全局变量的环境

ArrayEnv实现了用于记录函数的参数与局部变量的环境,但要记录全局变量,我们还需要另外设计一个不同的类,使用该类的对象来实现用于记录全局变量的环境。除了ArrayEnv类的功能,该类还需要随时记录变量的名称与变量值的保存位置(也就是数组元素的下标)之间的对应关系。它不仅能够通过编号查找变量值,还能通过变量名找到与之相应的变量值。

之前设计的Stone语言处理器可以在执行程序的同时以对话的形式添加新的语句。用户不必一次输入全部程序,从头至尾完整运行。因此,为了让之后添加的语句也能访问全局变量,我们必须始终记录变量的名称与该值保存位置的对应关系。
语言处理器必须能够通过变量名查找新添加语句中使用的变量值的保存位置。

另一方面,局部变量仅能在函数内部引用。函数在定义完成时就能确定所有引用了局部变量之处,且之后无法新增。这时,所有引用该变量的标识符都会在各自的Name对象中记录它的保存位置。由于语言处理器记录了这些信息之后便无需再了解变量名与保存位置的对应关系,因此环境不必记录变量的名称。作为用于记录局部变量的环境,ArrayEnv对象已经足够。

代码清单11.2中的ResizableArrayEnv类用于实现记录全局变量的环境。它是ArrayEnv的子类。ArrayEnv对象只能保存固定数量的变量,ResizableArrayEnv对象则能保存任意数量的变量。

由于程序新增的语句可能会引入新的全局变量,因此环境能够保存的变量数量也必须能够修改。ResizableArrayEnv类的对象含有names字段,它的值是一个Symbols对象。Symbols对象是一张哈希表,用于记录变量名与保存位置之间的对应关系。代码清单11.3是Symbols类的定义。

代码清单11.2 ResizableArrayEnv.java

package chap11;
import java.util.Arrays;
import chap6.Environment;
import chap11.EnvOptimizer.EnvEx2;

public class ResizableArrayEnv extends ArrayEnv {
    protected Symbols names;

    public ResizableArrayEnv() {
        super(10, null);
        names = new Symbols();
    }

    @Override
    public Symbols symbols() {
        return names;
    }

    @Override
    public Object get(String name) {
        Integer i = names.find(name);
        if (i == null)
            if (outer == null)
                return null;
            else
                return outer.get(name);
        else
            return values[i];
    }

    @Override
    public void put(String name, Object value) {
        Environment e = where(name);
        if (e == null)
            e = this;
        ((EnvEx2) e).putNew(name, value);
    }

    @Override
    public void putNew(String name, Object value) {
        assign(names.putNew(name), value);
    }

    @Override
    public Environment where(String name) {
        if (names.find(name) != null)
            return this;
        else if (outer == null)
            return null;
        else
            return ((EnvEx2) outer).where(name);
    }

    @Override
    public void put(int nest, int index, Object value) {
        if (nest == 0)
            assign(index, value);
        else
            super.put(nest, index, value);
    }

    protected void assign(int index, Object value) {
        if (index >= values.length) {
            int newLen = values.length * 2;
            if (index >= newLen)
                newLen = index + 1;
            values = Arrays.copyOf(values, newLen);
        }
        values[index] = value;
    }
}

代码清单11.3 Symbols.java

package chap11;
import java.util.HashMap;

public class Symbols {
    public static class Location {
        public int nest, index;

        public Location(int nest, int index) {
            this.nest = nest;
            this.index = index;
        }
    }

    protected Symbols outer;
    protected HashMap<String, Integer> table;

    public Symbols() {
        this(null);
    }

    public Symbols(Symbols outer) {
        this.outer = outer;
        this.table = new HashMap<String, Integer>();
    }

    public int size() {
        return table.size();
    }

    public void append(Symbols s) {
        table.putAll(s.table);
    }

    public Integer find(String key) {
        return table.get(key);
    }

    public Location get(String key) {
        return get(key, 0);
    }

    public Location get(String key, int nest) {
        Integer index = table.get(key);
        if (index == null)
            if (outer == null)
                return null;
            else
                return outer.get(key, nest + 1);
        else
            return new Location(nest, index.intValue());
    }

    public int putNew(String key) {
        Integer i = find(key);
        if (i == null)
            return add(key);
        else
            return i;
    }

    public Location put(String key) {
        Location loc = get(key, 0);
        if (loc == null)
            return new Location(0, add(key));
        else
            return loc;
    }

    protected int add(String key) {
        int i = table.size();
        table.put(key, i);
        return i;
    }
}

11.3 事先确认变量值的存放位置

接下来为抽象语法树中的类添加1ookup方法,它的作用是在函数定义时,查找函数用到的所有变量,并确定它们在环境中的保存位置。该方法还将根据需要,在抽象语法树的节点对象中记录这些保存位置。这样语言处理器就能够通过编号来查找保存在环境中的变量值

代码清单11.4是为抽象语法树的各个类添加lookup方法的修改器。这里仅对支持函数与闭包的Stone语言进行性能优化,不会涉及类的优化

lookup方法如果在遍历时发现了赋值表达式左侧的变量名,就会查找通过参数接收的Symbols对象,判断该变量名是否是第一次出现、尚未记录。如果它是首次出现的变量名,lookup方法将为它在环境中分配一个保存位置,在Symbols对象中记录由该变量名与保存位置组成的名值对。除了赋值,lookup方法还会在所有引用该变量的抽象语法树节点中记录变量值的保存位置

代码清单11.4 EnvOptimizer.java

package chap11;
import static javassist.gluonj.GluonJ.revise;
import javassist.gluonj.*;
import java.util.List;
import stone.Token;
import stone.StoneException;
import stone.ast.*;
import chap11.Symbols.Location;
import chap6.Environment;
import chap6.BasicEvaluator;
import chap7.ClosureEvaluator;

@Require(ClosureEvaluator.class)
@Reviser public class EnvOptimizer {
    @Reviser public static interface EnvEx2 extends Environment {
        Symbols symbols();
        void put(int nest, int index, Object value);
        Object get(int nest, int index);
        void putNew(String name, Object value);
        Environment where(String name);
    }
    @Reviser public static abstract class ASTreeOptEx extends ASTree {
        public void lookup(Symbols syms) {}
    }
    @Reviser public static class ASTListEx extends ASTList {
        public ASTListEx(List<ASTree> c) { super(c); }
        public void lookup(Symbols syms) {
            for (ASTree t: this)
                ((ASTreeOptEx)t).lookup(syms);
        }
    }
    @Reviser public static class DefStmntEx extends DefStmnt {
        protected int index, size;
        public DefStmntEx(List<ASTree> c) { super(c); }
        public void lookup(Symbols syms) {
            index = syms.putNew(name());
            size = FunEx.lookup(syms, parameters(), body());
        }
        public Object eval(Environment env) {
            ((EnvEx2)env).put(0, index, new OptFunction(parameters(), body(),
                                                        env, size));
            return name();
        }
    }
    @Reviser public static class FunEx extends Fun {
        protected int size = -1;
        public FunEx(List<ASTree> c) { super(c); }
        public void lookup(Symbols syms) {
            size = lookup(syms, parameters(), body());
        }
        public Object eval(Environment env) {
            return new OptFunction(parameters(), body(), env, size);
        }
        public static int lookup(Symbols syms, ParameterList params,
                                 BlockStmnt body)
        {
            Symbols newSyms = new Symbols(syms);
            ((ParamsEx)params).lookup(newSyms);
            ((ASTreeOptEx)revise(body)).lookup(newSyms);
            return newSyms.size();
        }
    }
    @Reviser public static class ParamsEx extends ParameterList {
        protected int[] offsets = null;
        public ParamsEx(List<ASTree> c) { super(c); }
        public void lookup(Symbols syms) {
            int s = size();
            offsets = new int[s];
            for (int i = 0; i < s; i++)
                offsets[i] = syms.putNew(name(i));
        }
        public void eval(Environment env, int index, Object value) {
            ((EnvEx2)env).put(0, offsets[index], value);
        }
    }
    @Reviser public static class NameEx extends Name {
        protected static final int UNKNOWN = -1;
        protected int nest, index;
        public NameEx(Token t) { super(t); index = UNKNOWN; }
        public void lookup(Symbols syms) {
            Location loc = syms.get(name());
            if (loc == null)
                throw new StoneException("undefined name: " + name(), this);
            else {
                nest = loc.nest;
                index = loc.index;
            }
        }
        public void lookupForAssign(Symbols syms) {
            Location loc = syms.put(name());
            nest = loc.nest;
            index = loc.index;
        }
        public Object eval(Environment env) {
            if (index == UNKNOWN)
                return env.get(name());
            else
                return ((EnvEx2)env).get(nest, index);
        }
        public void evalForAssign(Environment env, Object value) {
            if (index == UNKNOWN)
                env.put(name(), value);
            else
                ((EnvEx2)env).put(nest, index, value);
        }
    }
    @Reviser public static class BinaryEx2 extends BasicEvaluator.BinaryEx {
        public BinaryEx2(List<ASTree> c) { super(c); }
        public void lookup(Symbols syms) {
            ASTree left = left();
            if ("=".equals(operator())) {
                if (left instanceof Name) {
                    ((NameEx)left).lookupForAssign(syms);
                    ((ASTreeOptEx)right()).lookup(syms);
                    return;
                }
            }
            ((ASTreeOptEx)left).lookup(syms);
            ((ASTreeOptEx)right()).lookup(syms);
        }
        @Override
        protected Object computeAssign(Environment env, Object rvalue) {
            ASTree l = left();
            if (l instanceof Name) {
                ((NameEx)l).evalForAssign(env, rvalue);
                return rvalue;
            }
            else
                return super.computeAssign(env, rvalue);
        }
    }
}

11.4 修正eval方法并最终完成性能优化

代码清单11.4中的修改器将覆盖一些类的eval方法。如上所述,经过这些修改,eval方法将根据由lookup方法记录的保存位置,从环境中获取变量的值或对其进行更新

ParameterList类、Name类与BinaryExpr类的eval方法修改较为简单。Defstmnt类与Fun类的eval在修改后返回的将不再是Function类的对象,而是一个由代码清单11.5定义的OptFunction对象。OptFunction类是Function类的子类,OptFunction对象同样用于表示函数。两者的区别在于,OptFunction类将通过ArrayEnv对象来实现函数的执行环境

至此,所有修改都已完成。代码清单11.6与代码清单11.7分别是用于执行修改后的语言处理器的解释器,以及该解释器的启动程序

代码清单11.5 OptFunction.java

package chap11;
import stone.ast.BlockStmnt;
import stone.ast.ParameterList;
import chap6.Environment;
import chap7.Function;

public class OptFunction extends Function {
    protected int size;

    public OptFunction(ParameterList parameters, BlockStmnt body, Environment env, int memorySize) {
        super(parameters, body, env);
        size = memorySize;
    }

    @Override
    public Environment makeEnv() {
        return new ArrayEnv(size, env);
    }
}

代码清单11.6 EnvOptInterpreter.java

package chap11;
import chap6.BasicEvaluator;
import chap6.Environment;
import chap8.Natives;
import stone.BasicParser;
import stone.ClosureParser;
import stone.CodeDialog;
import stone.Lexer;
import stone.ParseException;
import stone.Token;
import stone.ast.ASTree;
import stone.ast.NullStmnt;

public class EnvOptInterpreter {
    public static void main(String[] args) throws ParseException {
        run(new ClosureParser(), new Natives().environment(new ResizableArrayEnv()));
    }

    public static void run(BasicParser bp, Environment env) throws ParseException {
        Lexer lexer = new Lexer(new CodeDialog());
        while (lexer.peek(0) != Token.EOF) {
            ASTree t = bp.parse(lexer);
            if (!(t instanceof NullStmnt)) {
                ((EnvOptimizer.ASTreeOptEx) t).lookup(((EnvOptimizer.EnvEx2) env).symbols());
                Object r = ((BasicEvaluator.ASTreeEx) t).eval(env);
                System.out.println("=> " + r);
            }
        }
    }
}

代码清单11.7 EnvOptRunner.java

package chap11;
import chap8.NativeEvaluator;
import javassist.gluonj.util.Loader;

public class EnvOptRunner {
    public static void main(String[] args) throws Throwable {
        Loader.run(EnvOptInterpreter.class, args, EnvOptimizer.class, NativeEvaluator.class);
    }
}

接下来可以通过第八天代码清单8.6中的计算斐波那契的程序来比较优化前后的执行时间,最终结果计算速度至少提升了70%

原文地址:https://www.cnblogs.com/ZCWang/p/12231283.html

时间: 2024-10-09 13:35:05

两周自制脚本语言-第11天 优化变量读写性能的相关文章

两周自制脚本语言-第7天 添加函数功能

第7天 添加函数功能 基本的函数定义与调用执行.引入闭包使Stone语言可以将变量赋值为函数,或将函数作为参数传递给其他函数 有些函数将有返回值的归为函数,没有返回值的归为子程序 7.1 扩充语法规则 函数定义语句的语法规则 此书将函数定义语句称为def语句.def语句仅能用于最外层代码,用户无法在代码块中定义函数 Stone语言将最后执行语句(表达式)的计算结果将作为函数的返回值返回 代码清单 7.1 与函数相关的语法规则 param : IDENTIFIER params : param {

两周自制脚本语言-第9天 设计面向对象语言

第9天 设计面向对象语言 目标:为Stone语言添加类和对象的支持.仅支持单一继承 9.1 设计用于操作类与对象的语法 添加的类与对象的处理功能后,下面的Stone语言就能被正确执行了 class Position { x = y = 0 def move(nx,ny) { x = nx; y = ny } } p = Position.new p.move(3, 4) p.x = 10 print p.x + p.y 首先定义一个Position类,方法由def语句定义.类中字段通过变量表示,

两周自制脚本语言-第5天 设计语法分析器

第5天 设计语法分析器 5.1 Stone语言的语法 代码清单 5.1 Stone 语言的语法定义 primary : "(" expr ")" | NUMBER | IDENTIFIER | STRING factor : "-" primary | primary expr : factor { OP factor } block : "{" [ statement ] { (";" | EOL) [

两周自制脚本语言-第6天 通过解释器执行程序

第6天 通过解释器执行程序 解释器从抽象语法树的根节点开始遍历该树直至叶节点,并计算各节点的内容 6.1 eval方法与环境对象 eval方法:eval是evaluate(求值)的缩写.eval方法将计算与该节点为根的子树对应的语句.表达式及子表达式,并返回执行结果. eval方法递归调用子节点的eval方法 不同类型的节点的类,对eval方法有着不同的定义 eval方法的简化版本 public Object eval(Environment env){ Object left = left()

两周自制脚本语言-第10天 无法割舍的数组

第10天 无法割舍的数组 目标:为Stone语言添加简单的数组功能,下标(index)只能使用整数值. 10.1扩展语法分析器 代码清单10.1 与数组相关的语法规则 elements : expr { "," expr } primary : ( "[" [ elements ] "]" | "(" expr ")" | NUMBER | IDENTIFIER | STRING ) { postfix }

如何在Java平台上使用脚本语言做Java开发

如何在Java平台上使用脚本语言做Java开发     最近开始流行区分Java平台和Java语言,但很多Java开发者还是不能确定如何在 Java应用程序开发中结合脚本.本篇文章,Gregor Roth给出了在Java平台上使用脚本的方法.通过这篇文章,你可以了解怎样在你的Java应用程序中使用脚本,是否你要通过使用Groovy和 Jython把不同的Java应用程序模块粘合在一起,或者写一个你自己的基于JRuby的应用程序,适用于Java平台. 作为一个Java开发者,你可能已经注意到了,J

JDK1.7新特性(3):java语言动态性之脚本语言API

简要描述:其实在jdk1.6中就引入了支持脚本语言的API.这使得java能够很轻松的调用其他脚本语言.具体API的使用参考下面的代码: 1 package com.rampage.jdk7.chapter2; 2 3 import java.io.FileWriter; 4 import java.io.IOException; 5 6 import javax.script.Bindings; 7 import javax.script.Compilable; 8 import javax.

[Java面试九]脚本语言知识总结.

核心内容概述 1.JavaScript加强,涉及到ECMAScript语法.BOM对象.DOM对象以及事件. 2.Ajax传统编程. 3.jQuery框架,九种选择器为核心学习内容 4.JQuery UI插件 5.jQuery Ajax编程 6.jQuery第三方插件 7.反向Ajax编程(彗星) 一.JavaScript基础加强 JavaScript是在浏览器内容运行,无需编译.解释执行动态脚本语言,是一种弱类型语言,所有变量使用var定义. JavaScript的3个组成部分分别为:核心(E

客户端脚本语言javascript

2015.11.27  客户端脚本语言javascript. (叫这个名字的原因.想要攀高枝,希望变得和他一样通用.关于名字之间的关系类似于雷锋和雷峰塔,巴基斯坦和卡巴斯基,苏格拉底跟格拉苏蒂的关系一样.阿迪王 和阿迪达斯)康师傅和康帅博 也是javaweb中的一个环节,javascript现在做的也很强大.主要应用于b/s部分的b也就是浏览器客户端的部分. 1.javascript语言,在运行的时候是不需要服务器的,只有浏览器就可以对js代码进行解释. 2.脚本语言:自身无法独立在浏览器端运行