《两周自制脚本语言》——第十二天(优化对象操作性能)

笔记、源码同步在github上,欢迎Star,蟹蟹~


### 减少内存占用 第9天的实现使用了环境来表现Stone语言中的对象。环境中不仅含有由字段名与相应的值组成的键值对,还记录了由方法名与Function对象组成的键值对。这种实现的内存利用率很低

JavaScript每个对象都能拥有不同方法的语言,这种实现方式较为合适。然而,Stone语言中同一个类的对象只能具有相同的方法。因此,语言处理器没有必要在环境中记录由方法名与Function对象组成的键值对

基于以上原因,今天的优化中,同一个类的对象将共享方法(见下图)。Stone语言将为每个方法创建一个ClassInfo对象,用于记录与方法相关的信息。用于表示Stone语言对象的stoneObject对象包含ClassInfo对象的引用,当语言处理器需要获取方法调用的相关信息时,将查找该ClassInfo对象中的内容。这样一来,Stoneobject对象仅需记录字段信息,每个单独的stoneobject对象使用的内存量也相应减少

今天的优化方法和第十一天一样,通过数组实现环境

根据上述讨论重新定义ClassInfo类与StoneObject类。为了区分今天和第九天的定义,重新定义的类的名称之前会加上Opt

代码清单12.1 OptClassInfo.java

package chap12;
import java.util.ArrayList;
import Stone.ast.ClassStmnt;
import Stone.ast.DefStmnt;
import chap11.Symbols;
import chap12.ObjOptimizer.DefStmntEx2;
import chap6.Environment;
import chap9.ClassInfo;

public class OptClassInfo extends ClassInfo {
	protected Symbols methods, fields;
	protected DefStmnt[] methodDefs;

	public OptClassInfo(ClassStmnt cs, Environment env, Symbols methods, Symbols fields) {
		super(cs, env);
		this.methods = methods;
		this.fields = fields;
		this.methodDefs = null;
	}

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

	@Override
	public OptClassInfo superClass() {
		return (OptClassInfo) superClass;
	}

	public void copyTo(Symbols f, Symbols m, ArrayList<DefStmnt> mlist) {
		f.append(fields);
		m.append(methods);
		for (DefStmnt def : methodDefs)
			mlist.add(def);
	}

	public Integer fieldIndex(String name) {
		return fields.find(name);
	}

	public Integer methodIndex(String name) {
		return methods.find(name);
	}

	public Object method(OptStoneObject self, int index) {
		DefStmnt def = methodDefs[index];
		return new OptMethod(def.parameters(), def.body(), environment(), ((DefStmntEx2) def).locals(), self);
	}

	public void setMethods(ArrayList<DefStmnt> methods) {
		methodDefs = methods.toArray(new DefStmnt[methods.size()]);
	}
}

代码清单12.2 OptStoneObject.java

package chap12;
import chap12.OptStoneObject.AccessException;

public class OptStoneObject {
	public static class AccessException extends Exception {
	}

	protected OptClassInfo classInfo;
	protected Object[] fields;

	public OptStoneObject(OptClassInfo ci, int size) {
		classInfo = ci;
		fields = new Object[size];
	}

	public OptClassInfo classInfo() {
		return classInfo;
	}

	public Object read(String name) throws AccessException {
		Integer i = classInfo.fieldIndex(name);
		if (i != null)
			return fields[i];
		else {
			i = classInfo.methodIndex(name);
			if (i != null)
				return method(i);
		}
		throw new AccessException();
	}

	public void write(String name, Object value) throws AccessException {
		Integer i = classInfo.fieldIndex(name);
		if (i == null)
			throw new AccessException();
		else
			fields[i] = value;
	}

	public Object read(int index) {
		return fields[index];
	}

	public void write(int index, Object value) {
		fields[index] = value;
	}

	public Object method(int index) {
		return classInfo.method(this, index);
	}
}

代码清单12.3 OptMethod.java

package chap12;
import Stone.ast.BlockStmnt;
import Stone.ast.ParameterList;
import chap11.ArrayEnv;
import chap11.OptFunction;
import chap6.Environment;

public class OptMethod extends OptFunction {
	OptStoneObject self;

	public OptMethod(ParameterList parameters, BlockStmnt body, Environment env, int memorySize, OptStoneObject self) {
		super(parameters, body, env, memorySize);
		this.self = self;
	}

	@Override
	public Environment makeEnv() {
		ArrayEnv e = new ArrayEnv(size, env);
		e.put(0, 0, self);
		return e;
	}
}

定义lookup方法

lookup方法能查找class语句中与由大括号括起的类定义体对应的抽象语法树,向Symbols对象添加该类种所有的方法名与字段名,同时为它们分配保存位置。lookup方法执行结束后,语言处理器将能通过Symbols对象获得方法名与字段名一览

下图是lookup方法在查找大括号括起的类定义体时使用的Symbo1s对象。这4个对象通过outer字段连接,分别记录了不同类型的名称。除了最后一个,这些对象都属于Symbo1s类的子类

扫描二维码关注公众号,回复: 8568668 查看本文章

代码清单12.4是图中第一个出现的SymbolThis对象的定义。它用于记录在类定义体中有效的局部变量的名称。然而,与函数不同,类定义体中新增的名称并非局部变量,而是字段名称,因此该作用域内的有效局部变量就只有一个this。SymbolThis对象仅会记录this的信息

下面这两个MemberSymbols对象分别用于记录字段名与方法名。代码清单12.5是它们的定义。如果lookup方法在类定义中遇到了用于定义方法的def语句,就会直接将该方法名称添加至第二个Membersymbols对象中

代码清单12.4 SymbolThis.java

package chap12;
import Stone.StoneException;
import chap11.Symbols;

public class SymbolThis extends Symbols {
	public static final String NAME = "this";

	public SymbolThis(Symbols outer) {
		super(outer);
		add(NAME);
	}

	@Override
	public int putNew(String key) {
		throw new StoneException("fatal");
	}

	@Override
	public Location put(String key) {
		Location loc = outer.put(key);
		if (loc.nest >= 0)
			loc.nest++;
		return loc;
	}
}

代码清单12.5 MemberSymbols.java

package chap12;
import chap11.Symbols;

public class MemberSymbols extends Symbols {
	public static int METHOD = -1;
	public static int FIELD = -2;
	protected int type;

	public MemberSymbols(Symbols outer, int type) {
		super(outer);
		this.type = type;
	}

	@Override
	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);
		else
			return new Location(type, index.intValue());
	}

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

整合所有修改并执行

代码清单12.6 ObjOptimizer.java

package chap12;
import java.util.ArrayList;
import java.util.List;
import static javassist.gluonj.GluonJ.revise;
import javassist.gluonj.*;
import Stone.*;
import Stone.ast.*;
import chap6.Environment;
import chap6.BasicEvaluator;
import chap6.BasicEvaluator.ASTreeEx;
import chap7.FuncEvaluator.PrimaryEx;
import chap11.ArrayEnv;
import chap11.EnvOptimizer;
import chap11.Symbols;
import chap11.EnvOptimizer.ASTreeOptEx;
import chap11.EnvOptimizer.EnvEx2;
import chap11.EnvOptimizer.ParamsEx;
import chap12.OptStoneObject.AccessException;

@Require(EnvOptimizer.class)
@Reviser
public class ObjOptimizer {
	@Reviser
	public static class ClassStmntEx extends ClassStmnt {
		public ClassStmntEx(List<ASTree> c) {
			super(c);
		}

		public void lookup(Symbols syms) {
		}

		public Object eval(Environment env) {
			Symbols methodNames = new MemberSymbols(((EnvEx2) env).symbols(), MemberSymbols.METHOD);
			Symbols fieldNames = new MemberSymbols(methodNames, MemberSymbols.FIELD);
			OptClassInfo ci = new OptClassInfo(this, env, methodNames, fieldNames);
			((EnvEx2) env).put(name(), ci);
			ArrayList<DefStmnt> methods = new ArrayList<DefStmnt>();
			if (ci.superClass() != null)
				ci.superClass().copyTo(fieldNames, methodNames, methods);
			Symbols newSyms = new SymbolThis(fieldNames);
			((ClassBodyEx) body()).lookup(newSyms, methodNames, fieldNames, methods);
			ci.setMethods(methods);
			return name();
		}
	}

	@Reviser
	public static class ClassBodyEx extends ClassBody {
		public ClassBodyEx(List<ASTree> c) {
			super(c);
		}

		public Object eval(Environment env) {
			for (ASTree t : this)
				if (!(t instanceof DefStmnt))
					((ASTreeEx) t).eval(env);
			return null;
		}

		public void lookup(Symbols syms, Symbols methodNames, Symbols fieldNames, ArrayList<DefStmnt> methods) {
			for (ASTree t : this) {
				if (t instanceof DefStmnt) {
					DefStmnt def = (DefStmnt) t;
					int oldSize = methodNames.size();
					int i = methodNames.putNew(def.name());
					if (i >= oldSize)
						methods.add(def);
					else
						methods.set(i, def);
					((DefStmntEx2) def).lookupAsMethod(fieldNames);
				} else
					((ASTreeOptEx) t).lookup(syms);
			}
		}
	}

	@Reviser
	public static class DefStmntEx2 extends EnvOptimizer.DefStmntEx {
		public DefStmntEx2(List<ASTree> c) {
			super(c);
		}

		public int locals() {
			return size;
		}

		public void lookupAsMethod(Symbols syms) {
			Symbols newSyms = new Symbols(syms);
			newSyms.putNew(SymbolThis.NAME);
			((ParamsEx) parameters()).lookup(newSyms);
			((ASTreeOptEx) revise(body())).lookup(newSyms);
			size = newSyms.size();
		}
	}

	@Reviser
	public static class DotEx extends Dot {
		public DotEx(List<ASTree> c) {
			super(c);
		}

		public Object eval(Environment env, Object value) {
			String member = name();
			if (value instanceof OptClassInfo) {
				if ("new".equals(member)) {
					OptClassInfo ci = (OptClassInfo) value;
					ArrayEnv newEnv = new ArrayEnv(1, ci.environment());
					OptStoneObject so = new OptStoneObject(ci, ci.size());
					newEnv.put(0, 0, so);
					initObject(ci, so, newEnv);
					return so;
				}
			} else if (value instanceof OptStoneObject) {
				try {
					return ((OptStoneObject) value).read(member);
				} catch (AccessException e) {
				}
			}
			throw new StoneException("bad member access: " + member, this);
		}

		protected void initObject(OptClassInfo ci, OptStoneObject obj, Environment env) {
			if (ci.superClass() != null)
				initObject(ci.superClass(), obj, env);
			((ClassBodyEx) ci.body()).eval(env);
		}
	}

	@Reviser
	public static class NameEx2 extends EnvOptimizer.NameEx {
		public NameEx2(Token t) {
			super(t);
		}

		@Override
		public Object eval(Environment env) {
			if (index == UNKNOWN)
				return env.get(name());
			else if (nest == MemberSymbols.FIELD)
				return getThis(env).read(index);
			else if (nest == MemberSymbols.METHOD)
				return getThis(env).method(index);
			else
				return ((EnvEx2) env).get(nest, index);
		}

		@Override
		public void evalForAssign(Environment env, Object value) {
			if (index == UNKNOWN)
				env.put(name(), value);
			else if (nest == MemberSymbols.FIELD)
				getThis(env).write(index, value);
			else if (nest == MemberSymbols.METHOD)
				throw new StoneException("cannot update a method: " + name(), this);
			else
				((EnvEx2) env).put(nest, index, value);
		}

		protected OptStoneObject getThis(Environment env) {
			return (OptStoneObject) ((EnvEx2) env).get(0, 0);
		}
	}

	@Reviser
	public static class AssignEx extends BasicEvaluator.BinaryEx {
		public AssignEx(List<ASTree> c) {
			super(c);
		}

		@Override
		protected Object computeAssign(Environment env, Object rvalue) {
			ASTree le = left();
			if (le instanceof PrimaryExpr) {
				PrimaryEx p = (PrimaryEx) le;
				if (p.hasPostfix(0) && p.postfix(0) instanceof Dot) {
					Object t = ((PrimaryEx) le).evalSubExpr(env, 1);
					if (t instanceof OptStoneObject)
						return setField((OptStoneObject) t, (Dot) p.postfix(0), rvalue);
				}
			}
			return super.computeAssign(env, rvalue);
		}

		protected Object setField(OptStoneObject obj, Dot expr, Object rvalue) {
			String name = expr.name();
			try {
				obj.write(name, rvalue);
				return rvalue;
			} catch (AccessException e) {
				throw new StoneException("bad member access: " + name, this);
			}
		}
	}
}

代码清单12.7 ObjOptInterpreter.java

package chap12;
import Stone.ClassParser;
import Stone.ParseException;
import chap11.EnvOptInterpreter;
import chap11.ResizableArrayEnv;
import chap8.Natives;

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

代码清单12.8 ObjOptRunner.java

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

public class ObjOptRunner {
	public static void main(String[] args) throws Throwable {
		Loader.run(ObjOptInterpreter.class, args, ObjOptimizer.class, NativeEvaluator.class);
	}
}

内联缓存

如果要对非this对象进行方法调用或字段引用,今天介绍的1ookup方法将不再有效,执行速度无法得到提升。这样一来,语言处理器在调用函数或引用字段时,就不得不通过名称来查找环境,从哈希表中获取对应的值,使执行速度大幅下降

为了缓解这一问题,我们将使用一种名为内联缓存(inline cache)的方法。首先,我们假设程序需要引用某个对象的字段。正如之前所讲,只有在实际执行后语言处理器才能确定该对象的类型。不过,根据经验可知,同一位置出现的对象通常是同一种类型。即使是采用面向对象思想写成的程序也是如此。我们能够利用这一规律优化处理器的性能。语言处理器可以在执行程序的同时查找字段的保存位置,并将该结果与对象所属的类型结对保存。之后,如果再次执行同一段程序,语言处理器将首先判断对象的类型,如果与之前相同,则直接使用上次的查找结果,减少了因查找保存位置造成的性能下降

代码清单12.9中的InlineCache修改器实现了内联缓存机制。它将覆盖Dot类的eval方法与BinaryExpr类的setField方法。此外,它还会为每个类添加用于实现缓存功能的classInfo字段与index字段(其中,Dot类还会额外新增一个isField字段)

Dot类的eval方法用于读取字段的值,或充当方法调用表达式。setField方法用于处理赋值表达式左侧是某个对象的字段时的情况。这里的setField方法正是代码清单12.6中由AssignEx修改器定义的setField方法。这两个方法都会将由修改器添加的classInfo字段的值,与当前正被执行方法调用或字段引用的对象类型进行比较。如果相同,则再次利用上一次保存的值

经过InlineCache修改器的修改后,解释器将支持内联缓存功能。解释器本身的程序与代码清单12.7中的相同,不过,应用了修改器的解释器需要通过代码清单12.10中的启动程序运行。它与代码清单12.10及更早的代码清单12.8中的启动程序大同小异,唯一的区别在于启动程序中的run方法将接收不同类型的修改器

代码清单12.9 InlineCache.java

package chap12;
import java.util.List;
import Stone.StoneException;
import Stone.ast.ASTree;
import Stone.ast.Dot;
import chap6.Environment;
import javassist.gluonj.*;

@Require(ObjOptimizer.class)
@Reviser
public class InlineCache {
	@Reviser
	public static class DotEx2 extends ObjOptimizer.DotEx {
		protected OptClassInfo classInfo = null;
		protected boolean isField;
		protected int index;

		public DotEx2(List<ASTree> c) {
			super(c);
		}

		@Override
		public Object eval(Environment env, Object value) {
			if (value instanceof OptStoneObject) {
				OptStoneObject target = (OptStoneObject) value;
				if (target.classInfo() != classInfo)
					updateCache(target);
				if (isField)
					return target.read(index);
				else
					return target.method(index);
			} else
				return super.eval(env, value);
		}

		protected void updateCache(OptStoneObject target) {
			String member = name();
			classInfo = target.classInfo();
			Integer i = classInfo.fieldIndex(member);
			if (i != null) {
				isField = true;
				index = i;
				return;
			}
			i = classInfo.methodIndex(member);
			if (i != null) {
				isField = false;
				index = i;
				return;
			}
			throw new StoneException("bad member access: " + member, this);
		}
	}

	@Reviser
	public static class AssignEx2 extends ObjOptimizer.AssignEx {
		protected OptClassInfo classInfo = null;
		protected int index;

		public AssignEx2(List<ASTree> c) {
			super(c);
		}

		@Override
		protected Object setField(OptStoneObject obj, Dot expr, Object rvalue) {
			if (obj.classInfo() != classInfo) {
				String member = expr.name();
				classInfo = obj.classInfo();
				Integer i = classInfo.fieldIndex(member);
				if (i == null)
					throw new StoneException("bad member access: " + member, this);
				index = i;
			}
			obj.write(index, rvalue);
			return rvalue;
		}
	}
}

代码清单12.10 InlineRunner.java

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

public class InlineRunner {
	public static void main(String[] args) throws Throwable {
		Loader.run(ObjOptInterpreter.class, args, InlineCache.class, NativeEvaluator.class);
	}
}

代码清单12.11 测试斐波那契数的计算时间(面向对象版本)

class Fib {
    fib0 = 0
    fib1 = 1
    def fib (n) {
        if n == 0 {
            fib0
        } else {
            if n == 1 {
                this.fib1
            } else {
                fib(n - 1) + this.fib(n - 2)
            }
        }
    }
}
t = currentTime()
f = Fib.new
f.fib 33
print currentTime() - t + " msec"

运行效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWbDVh9o-1569714341362)(https://s1.ax1x.com/2018/12/22/FyYBOH.gif#shadow)]

可以看到优化后执行所需的时间约6秒,第九天没优化的执行所需时间约8秒

第十二天的思维导图

发布了167 篇原创文章 · 获赞 162 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_37236745/article/details/101668207
今日推荐