兩週自制腳本語言-第11天 優化變量讀寫性能

第11天 優化變量讀寫性能

以變量值的讀寫爲例,向讀者介紹基於這種理念的語言處理器性能優化方式。java

11.1 經過簡單數組來實現環境

假如函數包含局部變量x與y,程序能夠事先將x設爲數組的第0個元素,將y設爲第1個元素,以此類推。這樣一來,語言處理器引用變量時就無需計算哈希值。也就是說,這是一個經過編號,而非名稱來查找變量值的環境segmentfault

爲了實現這種設計,語言處理器須要在函數定義完成後遍歷對應的抽象語法樹節點,獲取該節點使用的全部函數參數與局部變量。遍歷以後程序將獲得函數中用到的參數與局部變量的數量,因而肯定了用於保存這些變量的數組的長度數組

以後,語言處理器在實際調用函數,對變量的值進行讀寫操做時,將會直接引用數組中的元素。變量引用無需再像以前那樣經過在哈希表中查找變量名的方式實現。性能優化

肯定變量的值在數組中的保存位置以後,這些信息將被記錄於抽象語法樹節點對象的字段中。例如,程序中出現的變量名在抽象語法樹中以Name對象表示。這一Name對象將事先在字段中保存數組元素的下標,這樣語言處理器在須要引用該變量時,就能知道應該引用數組中的哪個元素。Name對象的eval方法將經過該字段來引用數組元素,得到變量的值。閉包

沒必要在程序執行時經過變量名來查找變量。app

若是但願在Name對象的字段中保存變量的引用,僅憑數組元素仍然不夠,還須要同時記錄與環境對應的做用域。環境將以嵌套結構實現閉包。爲此,Environment對象須要經過outer字段串連。此外,Name對象還要記錄環境所處的層數,即從最內層向外數起,當前環境在這一連串Environment對象中的排序位置。該信息保存於Name對象的nest字段中。index字段則用於記錄變量的值在與nest字段指向的環境對應的數組中,具體的保存位置。ide

圖11.1 x=2的抽象語法樹與環境

下圖是表示x=2的抽象語法樹。在該圖中,變量x的值保存於從最內層數起的第2個環境對應的數組中,所以Name對象的nest字段的值爲1(若是是最內層,則值爲0)。因爲變量x的值保存於該數組的第3個元素中,所以index字段的值爲2函數

file

代碼清單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%

相關文章
相關標籤/搜索