NVelocity 1.1 vs StringTemplate 3.2
拿NVelocity 1.1、StringTemplate 3.2和ASP.NET的ASPX页面做了个性能测试对比,对比结果如下:
结果就是,在2台不同的机器上,ASPX:StringTemplate:NVelocity分别为1.00:8.53:1.61和1.00:7.34:1.71,StringTemplate的性能最弱,NVelocity有点接近ASPX的解析效率。测试过程中还发现,几点:
1. 对StringTemplate,不应该连续使用StringTemplateGroup太多次,每一个任务新建一个StringTemplateGroup性能要好很多
2. 之前的某些版本,连续使用同一个StringTemplateGroup对象多次获取模板,存在严重的内存泄漏问题,性能也非常差,3.2版本性能好了很多,也没有出现内存泄漏
3. StringTemplate还有一些bug,例如下面列表测试中的Dictionary、DataTable,在3.2版本下无法读取到值(3.1的版本在列表情况下可以读取到DataTable)
测试用的项目文件: 下载
测试辅助类:
NVelocity模板contact-list.vm如下:
StringTemplate模板如下:
contact_list.st
ASPNET.aspx页面:

结果就是,在2台不同的机器上,ASPX:StringTemplate:NVelocity分别为1.00:8.53:1.61和1.00:7.34:1.71,StringTemplate的性能最弱,NVelocity有点接近ASPX的解析效率。测试过程中还发现,几点:
1. 对StringTemplate,不应该连续使用StringTemplateGroup太多次,每一个任务新建一个StringTemplateGroup性能要好很多
2. 之前的某些版本,连续使用同一个StringTemplateGroup对象多次获取模板,存在严重的内存泄漏问题,性能也非常差,3.2版本性能好了很多,也没有出现内存泄漏
3. StringTemplate还有一些bug,例如下面列表测试中的Dictionary、DataTable,在3.2版本下无法读取到值(3.1的版本在列表情况下可以读取到DataTable)
测试用的项目文件: 下载
测试辅助类:
using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.IO; using System.Web; using Antlr3.ST; using Commons.Collections; using NVelocity; using NVelocity.App; using NVelocity.Runtime;测试用的页面:
public class Contact { public string Name { get; set; } public string EMail { get; set; } public string Address { get; set; } public decimal TotalOrderAmt { get; set; } //StringTemplate不支持模板中的比较判断,因此在这里处理 public bool IsLevel3 { get { return this.TotalOrderAmt > 1000; } } public bool IsLevel2 { get { return this.TotalOrderAmt <= 1000 && this.TotalOrderAmt > 200; } } public bool IsLevel1 { get { return this.TotalOrderAmt <= 200; } } }
public class TemplateUtil { private static string ST_ROOT_PATH = string.Empty; static TemplateUtil() { string rootDir = HttpContext.Current.Server.MapPath("./st"); if (rootDir.EndsWith("\\")) rootDir = rootDir.Substring(0, rootDir.Length - 1); ST_ROOT_PATH = rootDir; } public static void WriteTime(string title, long time) { HttpContext.Current.Response.Write("< br />" + title); long millisecondes = time % 1000; long minutes = time / 1000 / 60; long seconds = (time - minutes * 60 * 1000) / 1000; if (minutes > 0) HttpContext.Current.Response.Write(minutes.ToString() + "m, "); if (seconds > 0) HttpContext.Current.Response.Write(seconds.ToString() + "s, "); HttpContext.Current.Response.Write(millisecondes.ToString() + "ms"); } public static long NVelocityTest(int loops) { VelocityEngine engine = new VelocityEngine(); ExtendedProperties prop = new ExtendedProperties(); prop.AddProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH , HttpContext.Current.Server.MapPath("./")); prop.AddProperty(RuntimeConstants.ENCODING_DEFAULT, "utf-8"); engine.Init(prop); Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < loops; i++) NVelocityParse(engine, i, false); sw.Stop(); WriteTime("Total Elapsed Time: ", sw.ElapsedMilliseconds); return sw.ElapsedMilliseconds; } public static void NVelocityParse(VelocityEngine engine, int loop, bool output) { Template template = engine.GetTemplate("contact-list.vm"); VelocityContext context = new VelocityContext(); context.Put("contacts", new List() { new Contact() { Name="Richie Liu", TotalOrderAmt=180, EMail="[email protected]", Address="上海徐汇区钦州路" }, new Contact() { Name="Kevin Zhang",TotalOrderAmt=0, EMail="[email protected]", Address="苏州新区" }, new Contact() { Name="Eric Wong", TotalOrderAmt=626, EMail="[email protected]", Address="厦门海沧" }, new Contact() { Name="RicCC", TotalOrderAmt=2080, EMail="[email protected]", Address="上海徐汇区钦州路" } }); context.Put("qryName", "Ric"); context.Put("qryAddress", DateTime.Now); context.Put("hasMessage", true); context.Put("message", "This is a message from the server.");
context.Put("items1", new string[] { "AAA", "BBB", "CCC" }); context.Put("items2" , new List{ new DateTime(1979, 1, 1), new DateTime(2010, 4, 1) });
IDictionarydic = new Dictionary (); dic.Add("AAA", 111M); dic.Add("BBB", 222M); dic.Add("CCC", 333M); context.Put("items3", dic);
DataTable table = new DataTable(); table.Columns.Add(new DataColumn("Key", typeof(string))); table.Columns.Add(new DataColumn("Value", typeof(int))); DataRow row = table.NewRow(); row["Key"] = "item 1"; row["Value"] = 111; table.Rows.Add(row); row = table.NewRow(); row["Key"] = "item 2"; row["Value"] = 222; table.Rows.Add(row); context.Put("items4", table);
if (!output) { StringWriter writer = new StringWriter(); template.Merge(context, writer); writer.Close(); } else template.Merge(context, HttpContext.Current.Response.Output); } public static long StringTemplateTest(int loops) { Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < loops; i++) StringTemplateParse(i, false); sw.Stop(); WriteTime("Total Elapsed Time: ", sw.ElapsedMilliseconds); return sw.ElapsedMilliseconds; } public static void StringTemplateParse(int loop, bool output) { StringTemplateGroup group = new StringTemplateGroup("stperf", ST_ROOT_PATH);
StringTemplate st = group.LookupTemplate("contact_list"); st.SetAttribute("contacts", new List() { new Contact() { Name="Richie Liu", TotalOrderAmt=180, EMail="[email protected]", Address="上海徐汇区钦州路" }, new Contact() { Name="Kevin Zhang",TotalOrderAmt=0, EMail="[email protected]", Address="苏州新区" }, new Contact() { Name="Eric Wong", TotalOrderAmt=626, EMail="[email protected]", Address="厦门海沧" }, new Contact() { Name="RicCC", TotalOrderAmt=2080, EMail="[email protected]", Address="上海徐汇区钦州路" } }); st.SetAttribute("qryName", "Ric"); st.SetAttribute("qryAddress", DateTime.Now); st.SetAttribute("hasMessage", true); st.SetAttribute("message", "This is a message from the server.");
st.SetAttribute("items1", new string[] { "AAA", "BBB", "CCC" }); st.SetAttribute("items2" , new List{ new DateTime(1979, 1, 1), new DateTime(2010, 4, 1) });
Dictionarydic = new Dictionary (); dic.Add("AAA", 111M); dic.Add("BBB", 222M); dic.Add("CCC", 333M); st.SetAttribute("items3", dic);
DataTable table = new DataTable(); table.Columns.Add(new DataColumn("Key1", typeof(string))); table.Columns.Add(new DataColumn("Value1", typeof(int))); DataRow row = table.NewRow(); row["Key1"] = "item 1"; row["Value1"] = 111; table.Rows.Add(row); row = table.NewRow(); row["Key1"] = "item 2"; row["Value1"] = 222; table.Rows.Add(row); st.SetAttribute("items4", table);
if (!output) st.ToString(); else HttpContext.Current.Response.Write(st.ToString()); } public static long ASPXTest(int loops) { Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < loops; i++) ASPXParse(i); sw.Stop(); WriteTime("Total Elapsed Time: ", sw.ElapsedMilliseconds); return sw.ElapsedMilliseconds; } public static void ASPXParse(int loop) { StringWriter writer = new StringWriter(); HttpContext.Current.Server.Execute("ASPNET.aspx", writer); writer.Close(); } }
protected void Page_Load(object sender, EventArgs e) { this.Response.Write("NVelocity: "); Listtimes = new List (5); times.Add(TemplateUtil.NVelocityTest(10000)); times.Add(TemplateUtil.NVelocityTest(10000)); times.Add(TemplateUtil.NVelocityTest(10000)); times.Add(TemplateUtil.NVelocityTest(10000)); times.Add(TemplateUtil.NVelocityTest(10000)); TemplateUtil.WriteTime("Average Time: ", Convert.ToInt64(times.Average()));
this.Response.Write("< br />< br />StringTemplate: "); times = new List(5); times.Add(TemplateUtil.StringTemplateTest(1000)); times.Add(TemplateUtil.StringTemplateTest(1000)); times.Add(TemplateUtil.StringTemplateTest(1000)); times.Add(TemplateUtil.StringTemplateTest(1000)); times.Add(TemplateUtil.StringTemplateTest(1000)); TemplateUtil.WriteTime("Average Time: ", Convert.ToInt64(times.Average()));
this.Response.Write("< br />< br />ASP.NET: "); times = new List(5); times.Add(TemplateUtil.ASPXTest(10000)); times.Add(TemplateUtil.ASPXTest(10000)); times.Add(TemplateUtil.ASPXTest(10000)); times.Add(TemplateUtil.ASPXTest(10000)); times.Add(TemplateUtil.ASPXTest(10000)); TemplateUtil.WriteTime("Average Time: ", Convert.ToInt64(times.Average())); }
NVelocity模板contact-list.vm如下:
姓名: 地址:
Contact List
#foreach($c in $contacts) #beforeall#nodata No contacts found! #end
#odd 姓名 邮箱 地址 会员等级 #even #each #afterall$c.set_Name("$c.name - W") $c.get_Name() $c.EMail $c.Address #if($c.TotalOrderAmt>1000)钻卡会员 #elseif($c.TotalOrderAmt>200)金卡会员 #else Standard#end #after
#if($hasMessage) #set($msg="$message < br />--this message was append in NVelocity.") #end
< br />Item List - Array
#foreach($item in $items1) #each $item #between , #end
< br />Item List - List
#foreach($item in $items2) #each $item.ToString("yyyy-MM-dd") #between , #end
< br />Item List - Dictionary
#foreach($item in $items3) #each { $item.Key - $item.Value } #between ,#end
< br />Item List - DataTable
#foreach($item in $items4.Rows) { $item.Key - $item.Value } #between , #end
StringTemplate模板如下:
contact_list.st
row.st$!this is a comment!$
姓名: 地址: Contact List
$contacts:row(class="list-odd"),row(class="list-even")$ 姓名 邮箱 地址 会员等级
$if(hasMessage)$ $endif$
< br />Item List - Array
$items1:{$attr$};separator=","$
< br />Item List - List
$items2:{$attr$};separator=","$
< br />Item List - Dictionary
$items3:{{ $it$ }};separator=","$
< br />Item List - DataTable
$items4.Rows:{{ $attr$ }};separator=","$
$attr.Name$ $attr.EMail$ $attr.Address$ $if(attr.IsLevel3)$ 钻卡会员 $endif$ $if(attr.IsLevel2)$ 金卡会员 $endif$ $if(attr.IsLevel1)$ Standard $endif$
ASPNET.aspx页面:
ASPNET.aspx.cs:
姓名: 地址:
Contact List
<% for (int i = 0; i < this.contacts.Count; i++) { if ((i + 1) % 2 == 1) { %> 姓名 邮箱 地址 会员等级 <% } else { %> <% } %> <% } %><% this.contacts[i].Name = this.contacts[i].Name + " - W"; %><%= this.contacts[i].Name %> <%= this.contacts[i].EMail %> <%= this.contacts[i].Address %> <% if(this.contacts[i].TotalOrderAmt>1000){ %>钻卡会员 <% } else if (this.contacts[i].TotalOrderAmt > 200) { %>金卡会员 <%} else { %>Standard<% } %>
<% if (this.hasMessage) { string msg = this.message + "< br />--this message was append in NVelocity."; %> <%} %>
< br />Item List - Array
<% foreach(string s in this.items1){ %> <%= s %>, <%} %>
< br />Item List - List
<% foreach(DateTime dt in this.items2){ %> <%= dt.ToString("yyyy-MM-dd") %>, <%} %>
< br />Item List - Dictionary
<% foreach(System.Collections.Generic.KeyValuePairkv in this.items3){ %> {<%= kv.Key %>-<%= kv.Value.ToString() %>}, <%} %>
< br />Item List - DataTable
<% foreach(System.Data.DataRow row in this.items4.Rows ){ %> {<%= row["key"] %>-<%= row["value"]%>}, <%} %>
public string qryName; public string qryAddress; public IListcontacts; public bool hasMessage; public string message; public string[] items1; public List items2; public IDictionary items3; public DataTable items4;
protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { this.qryName = "Ric"; this.qryAddress = DateTime.Now.ToString(); this.hasMessage = true; this.message = "This is a message from the server."; this.contacts = new List() { new Contact() { Name="Richie Liu", TotalOrderAmt=180, EMail="[email protected]", Address="上海徐汇区钦州路" }, new Contact() { Name="Kevin Zhang",TotalOrderAmt=0, EMail="[email protected]", Address="苏州新区" }, new Contact() { Name="Eric Wong", TotalOrderAmt=626, EMail="[email protected]", Address="厦门海沧" }, new Contact() { Name="RicCC", TotalOrderAmt=2080, EMail="[email protected]", Address="上海徐汇区钦州路" } }; this.items1 = new string[] { "AAA", "BBB", "CCC" }; this.items2 = new List { new DateTime(1979, 1, 1), new DateTime(2010, 4, 1) }; this.items3 = new Dictionary (); this.items3.Add("AAA", 111M); this.items3.Add("BBB", 222M); this.items3.Add("CCC", 333M); DataTable table = new DataTable(); table.Columns.Add(new DataColumn("Key", typeof(string))); table.Columns.Add(new DataColumn("Value", typeof(int))); DataRow row = table.NewRow(); row["Key"] = "item 1"; row["Value"] = 111; table.Rows.Add(row); row = table.NewRow(); row["Key"] = "item 2"; row["Value"] = 222; table.Rows.Add(row); this.items4 = table; } }