比Velocity快10倍的模板引擎

在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属性的指令,如:

<table if="user.role == 'admin'">
<tr foreach="book in books">
<td>${book.title}</td>
</tr>
</table>


[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("<table><tr><td>");
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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值