在07年的时候,写过一个模板引擎,当时叫CommonTemplate,以前JavaEye有个开源系列介绍:[url]http://www.iteye.com/news/3381[/url],后来功能越来越多,性能却越来越差,在金大为发给我[url="http://code.google.com/p/templatetest/wiki/Velocity_CommonTemplate_XMLTemplate_Compare"]性能对比结果[/url]后,看到惨不忍睹的差距,就打算抛弃原设计进行重写,但因工作忙,就搁置了,最近看温少发了几个EL和JSON的解析器,有点手痒,就抽了个周未,拿出来再改了改,主要将模板改成了字节码编译,并简化了语法及缩小使用范围,只针对HTML场景使用,并将名称改成了HTTL,名字含义是把HTML中的M(Marker)改成了T(Template),放在GoogleCode上:[url]http://code.google.com/p/httl[/url],性能和Java硬编码输出模板内容差不多,比Velocity/FreeMarker等快10倍左右。
[b]语法方面的区别:[/b]
发现基于文本指令的,基于HTML标签的,基于HTML注释的,都有不少模板引擎实现,
为了标新立异以及使用的直观性,HTTL采用基于HTML属性的指令,如:
[b]选型方面的区别:[/b]
[*] Velocity采用JavaCC编译成AST树,解释执行。
[*] FreeMarker类似,只是采用FreeCC。
[*] Smarty4j采用ASM生成字节码,对条件等字节码的生成比JDK稍逊,而且需在运行时判断context变量的类型,无法强类型编译模板。
[*] HTTL采用先将模板转译成Java代码,再由JDK或Javassist编译成字节码,并在模板上声明传入类型,在编译期就推演所有变量类型,减少反射和运行时类型判断。
[b]部分优化策略示例:[/b]
[b](1) 强类型编译[/b]
对于表达式${user.name}的编译:
Smarty4j弱类型字节码生成:
Object user = context.get("user"); // 无法确定user是Map还是POJO // 反射获取属性的值,而且要运行期判断是user.getName(),还是user.name字段 Object name = ReflectUtil.get(user, "name"); // 接下来name也要反射
Httl强类型字节码生成:
User user = (User)context.get("user"); // 通过in="User user"声明类型 String name = user.getName(); // 编译期通过User的字段类型推演name的类型,并在编译期决定使用getName()
如果只是编译成弱类型字节码,性能比解释执行快不了多少,淘宝编译Velocity的测试数据显示,只能比JavaCC的AST解释执行快10%左右,参见附件的PPT。(附件的PPT是蒋江涛在InfoQ大会的演讲稿,是公开的)
[b](2) 对大模板拆分子函数:[/b](未发布)
SunJDK缺省对大于5K字节码的方法不进行JIT优化,
所以当模板的内容较大时,会导致生成的字节码也比较大,
通过拆分子函数,可以解决JIT优化问题。
淘宝编译Velocity的测试数据显示,大模板拆分成小模板性能提升35%,参见附件的PPT。
[b](3) 编译时就将文本编译成字节,加快输出:[/b]
原编译:
writer.write(""); writer.write(user.getName());
改为编译成:
output.write( new byte[] {60, 116, 97, 98, 108, 101, 62, 60, 116, 114, 62, 60, 116, 100, 62};); output.write(user.getName().getBytes());
淘宝编译Velocity的测试数据显示,将String输出预编译为byte[]输出,性能提升50%,参见附件的PPT。
[b](4) 对同条件if语句优化:[/b](未发布)
if (user.role == "admin") { // ... } else if (user.role == "member") { // ... } else { // ... }
优化后:
int id = System.identityHashCode(user.role); switch (id) { case 3452345: // 编译时计算"admin"的identityHashCode // ... case 2342452: // 编译时计算"member"的identityHashCode // ... default: // ... }
[b](5) 对于赋值生成的price为局部变量,不put回context:[/b]
比如:set="price = price * discount / 100"编译:
int price = price * discount / 100;
除非声明out="price",才在模板渲染最后:
context.put("price", price);
[b](6) 减少int到Integer等元类型的boxed和unboxed,以及instanceof。[/b]
因为模板输出的大量是基本类型和字符串,
比如当输出基本类型时,需要转成String,如果使用format(Object)接口,就会将基本类型装箱,
Httl遇到任何类似需要boxed和unboxed的地方,都会重载所有基本类型方法,以减少boxed和unboxed的处理。
出现instanceof的地方也一样,会尽量多态处理。
[b](7) 所有编译过程都会保持和计算源码位置,[/b]
当出错时,错误信息能准确定位到出错行列。
等等。
[b]性能测试:[/b]
[*] 模板内循环显示100行数据。
[*] 每模板各运行一万次。
[*] 模板大小约800字符。
[*] 模板每次运行输出内容约27K字符。
[*] 测试类:[url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/java/com/googlecode/httl/test/PerformanceTest.java"]PerformanceTest.java[/url]
[b]测试结果:[/b]
[table]
| Engine | 编译时间 | 运行一万次时间| 每秒处理数 | 模板 | 测试类 |
| Freemarker | 125ms | 16,934ms | 590t/s | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/resources/templates/books.ftl"]books.ftl[/url] | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/java/com/googlecode/httl/test/cases/FreemarkerCase.java"]FreemarkerCase.java[/url] |
| Velocity | 110ms | 19,278ms | 518t/s | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/resources/templates/books.vm"]books.vm[/url] | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/java/com/googlecode/httl/test/cases/VelocityCase.java"]VelocityCase.java[/url] |
| Smarty4j | 78ms | 21,653ms | 461t/s | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/resources/templates/books.st"]books.st[/url] | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/java/com/googlecode/httl/test/cases/Smarty4jCase.java"]Smarty4jCase.java[/url] |
| Httl | 547ms | 2,077ms | 4,814t/s | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/resources/templates/books.httl"]books.httl[/url] | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/java/com/googlecode/httl/test/cases/HttlCase.java"]HttlCase.java[/url] |
| Java | 0ms | 2,016ms | 4,960t/s | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/java/templates/Books.java"]Books.java[/url] | [url="http://httl.googlecode.com/svn/trunk/httl-test/src/test/java/com/googlecode/httl/test/cases/JavaCase.java"]JavaCase.java[/url] |
[/table]
[b]测试代码:[/b]
[url]http://code.google.com/p/httl/downloads/list[/url]
更多信息参见:
[url]http://code.google.com/p/httl[/url]
HTTL缺省使用Jdk的javax,tools编译字节码,需要500ms左右,如果换成Javassist编译,编译时间可以降到200ms左右,但字节码执行效率略差一点,但每个模板只编译一次,所以编译慢点也能忍受,如果想换成Javassist,只需在httl.properties中加入:
[code]
compiler=com.googlecode.httl.support.compilers.JavassistCompiler
java.version=1.4
[/code]
注:Javassist不支持1.5的语法,所以要设置java.version=1.4
你可能感兴趣的:(比Velocity快10倍的模板引擎)