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
row.st
ASPNET.aspx页面:
ASPNET.aspx.cs:
结果就是,在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)
测试用的项目文件: 下载
测试辅助类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | 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; <br> 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; } } } <br> 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<Contact>() { new Contact() { Name= "Richie Liu" , TotalOrderAmt=180, EMail= "RichieLiu-test@gmail.com" , Address= "上海徐汇区钦州路" }, new Contact() { Name= "Kevin Zhang" ,TotalOrderAmt=0, EMail= "KevinZhang-test@gmail.com" , Address= "苏州新区" }, new Contact() { Name= "Eric Wong" , TotalOrderAmt=626, EMail= "EricWong-test@gmail.com" , Address= "厦门海沧" }, new Contact() { Name= "RicCC" , TotalOrderAmt=2080, EMail= "RicCC-test@gmail.com" , Address= "上海徐汇区钦州路" } }); context.Put( "qryName" , "Ric" ); context.Put( "qryAddress" , DateTime.Now); context.Put( "hasMessage" , true ); context.Put( "message" , "This is a message from the server." ); <br> context.Put( "items1" , new string [] { "AAA" , "BBB" , "CCC" }); context.Put( "items2" , new List<DateTime> { new DateTime(1979, 1, 1), new DateTime(2010, 4, 1) }); <br> IDictionary< string , decimal > dic = new Dictionary< string , decimal >(); dic.Add( "AAA" , 111M); dic.Add( "BBB" , 222M); dic.Add( "CCC" , 333M); context.Put( "items3" , dic); <br> 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); <br> 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); <br> StringTemplate st = group .LookupTemplate( "contact_list" ); st.SetAttribute( "contacts" , new List<Contact>() { new Contact() { Name= "Richie Liu" , TotalOrderAmt=180, EMail= "RichieLiu-test@gmail.com" , Address= "上海徐汇区钦州路" }, new Contact() { Name= "Kevin Zhang" ,TotalOrderAmt=0, EMail= "KevinZhang-test@gmail.com" , Address= "苏州新区" }, new Contact() { Name= "Eric Wong" , TotalOrderAmt=626, EMail= "EricWong-test@gmail.com" , Address= "厦门海沧" }, new Contact() { Name= "RicCC" , TotalOrderAmt=2080, EMail= "RicCC-test@gmail.com" , Address= "上海徐汇区钦州路" } }); st.SetAttribute( "qryName" , "Ric" ); st.SetAttribute( "qryAddress" , DateTime.Now); st.SetAttribute( "hasMessage" , true ); st.SetAttribute( "message" , "This is a message from the server." ); <br> st.SetAttribute( "items1" , new string [] { "AAA" , "BBB" , "CCC" }); st.SetAttribute( "items2" , new List<DateTime> { new DateTime(1979, 1, 1), new DateTime(2010, 4, 1) }); <br> Dictionary< string , decimal > dic = new Dictionary< string , decimal >(); dic.Add( "AAA" , 111M); dic.Add( "BBB" , 222M); dic.Add( "CCC" , 333M); st.SetAttribute( "items3" , dic); <br> 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); <br> 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(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | protected void Page_Load( object sender, EventArgs e) { this .Response.Write( "NVelocity: " ); List< long > times = new List< long >(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())); <br> this .Response.Write( "< br />< br />StringTemplate: " ); times = new List< long >(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())); <br> this .Response.Write( "< br />< br />ASP.NET: " ); times = new List< long >(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如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | < div class="query-form"> < table > < tr > < td >姓名:</ td > < td >< input type="text" id="txtQryName" class="text" value="$qryName" /></ td > < td >地址:</ td > < td >< input type="text" id="txtQryAddress" class="text" value="$qryAddress" /></ td > < td >< input type="button" id="btnQuery" value="查询" /></ td > </ tr > </ table > </ div > < br > < h2 >Contact List</ h2 > < div class="contact-list"> #foreach($c in $contacts) #beforeall < table > < tr class="list-header"> < td >姓名</ td > < td >邮箱</ td > < td >地址</ td > < td >会员等级</ td > </ tr > #odd < tr class="list-odd"> #even < tr class="list-even"> #each < td >$c.set_Name("$c.name - W") $c.get_Name()</ td > < td >< a href="mailto:$c.EMail">$c.EMail</ a ></ td > < td >$c.Address</ td > < td >#if($c.TotalOrderAmt>1000)钻卡会员 #elseif($c.TotalOrderAmt>200)金卡会员 #else Standard#end</ td > #after </ tr > #afterall </ table > #nodata No contacts found! #end </ div > < br > #if($hasMessage) #set($msg="$message < br />--this message was append in NVelocity.") < div class="message">$msg</ div > #end < br > < br /> < h2 >Item List - Array</ h2 > < div > #foreach($item in $items1) #each $item #between , #end </ div > < br > < br /> < h2 >Item List - List</ h2 > < div > #foreach($item in $items2) #each $item.ToString("yyyy-MM-dd") #between , #end </ div > < br > < br /> < h2 >Item List - Dictionary</ h2 > < div > #foreach($item in $items3) #each { $item.Key - $item.Value } #between ,#end </ div > < br > < br /> < h2 >Item List - DataTable</ h2 > < div > #foreach($item in $items4.Rows) { $item.Key - $item.Value } #between , #end </ div > |
StringTemplate模板如下:
contact_list.st
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | < div class="query-form"> < table > < tr > < td >姓名:</ td > < td >< input type="text" id="txtQryName" class="text" value="$qryName$" /></ td > < td >地址:</ td > < td >< input type="text" id="txtQryAddress" class="text" value="$qryAddress$" /></ td > < td >< input type="button" id="btnQuery" value="查询" /></ td > </ tr > </ table > </ div > $!this is a comment!$ < h2 >Contact List</ h2 > < div class="contact-list"> < table > < tr class="list-header"> < td >姓名</ td > < td >邮箱</ td > < td >地址</ td > < td >会员等级</ td > </ tr > $contacts:row(class="list-odd"),row(class="list-even")$ </ table > </ div > < br > $if(hasMessage)$ < div class="message">$message$ - < br />--this message was append in NVelocity.</ div > $endif$ < br > < br /> < h2 >Item List - Array</ h2 > < div > $items1:{$attr$};separator=","$ </ div > < br > < br /> < h2 >Item List - List</ h2 > < div > $items2:{$attr$};separator=","$ </ div > < br > < br /> < h2 >Item List - Dictionary</ h2 > < div > $items3:{{ $it$ }};separator=","$ </ div > < br > < br /> < h2 >Item List - DataTable</ h2 > < div > $items4.Rows:{{ $attr$ }};separator=","$ </ div > |
1 2 3 4 5 6 7 8 9 | < tr class="$class$"> < td >$attr.Name$</ td > < td >< a href="mailto:$attr.EMail$">$attr.EMail$</ a ></ td > < td >$attr.Address$</ td > < td > $if(attr.IsLevel3)$ 钻卡会员 $endif$ $if(attr.IsLevel2)$ 金卡会员 $endif$ $if(attr.IsLevel1)$ Standard $endif$</ td > </ tr > |
ASPNET.aspx页面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | < div class="query-form"> < table > < tr > < td >姓名:</ td > < td >< input type="text" id="txtQryName" class="text" value='<%= this.qryName %>' /></ td > < td >地址:</ td > < td >< input type="text" id="txtQryAddress" class="text" value='<%= this.qryAddress %>' /></ td > < td >< input type="button" id="btnQuery" value="查询" /></ td > </ tr > </ table > </ div > < br > < h2 >Contact List</ h2 > < div class="contact-list"> < table > < tr class="list-header"> < td >姓名</ td > < td >邮箱</ td > < td >地址</ td > < td >会员等级</ td > </ tr > <% for (int i = 0; i < this.contacts.Count ; i++) { if ((i + 1) % 2 == 1) { %> < tr class="list-odd"> <% } else { %> < tr class="list-even"> <% } %> < td ><% this.contacts[i].Name = this.contacts[i].Name + " - W"; %><%= this.contacts[i].Name %></ td > < td >< a href='<%= this.contacts[i].EMail %>'><%= this.contacts[i].EMail %></ a ></ td > < td ><%= this.contacts[i].Address %></ td > < td ><% if(this.contacts[i].TotalOrderAmt>1000){ %>钻卡会员 <% } else if (this.contacts[i].TotalOrderAmt > 200) { %>金卡会员 <%} else { %>Standard<% } %></ td > </ tr > <% } %> </ table > </ div > < br > <% if (this.hasMessage) { string msg = this.message + "< br />--this message was append in NVelocity."; %> < div class="message"><%= msg %></ div > <%} %> < br > < br /> < h2 >Item List - Array</ h2 > < div > <% foreach(string s in this.items1){ %> <%= s %>, <%} %> </ div > < br > < br /> < h2 >Item List - List</ h2 > < div > <% foreach(DateTime dt in this.items2){ %> <%= dt.ToString("yyyy-MM-dd") %>, <%} %> </ div > < br > < br /> < h2 >Item List - Dictionary</ h2 > < div > <% foreach(System.Collections.Generic.KeyValuePair< string , decimal> kv in this.items3){ %> {<%= kv.Key %>-<%= kv.Value.ToString() %>}, <%} %> </ div > < br > < br /> < h2 >Item List - DataTable</ h2 > < div > <% foreach(System.Data.DataRow row in this.items4.Rows ){ %> {<%= row["key"] %>-<%= row["value"]%>}, <%} %> </ div > |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | public string qryName; public string qryAddress; public IList<Contact> contacts; public bool hasMessage; public string message; public string [] items1; public List<DateTime> items2; public IDictionary< string , decimal > items3; public DataTable items4; <br> 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<Contact>() { new Contact() { Name= "Richie Liu" , TotalOrderAmt=180, EMail= "RichieLiu-test@gmail.com" , Address= "上海徐汇区钦州路" }, new Contact() { Name= "Kevin Zhang" ,TotalOrderAmt=0, EMail= "KevinZhang-test@gmail.com" , Address= "苏州新区" }, new Contact() { Name= "Eric Wong" , TotalOrderAmt=626, EMail= "EricWong-test@gmail.com" , Address= "厦门海沧" }, new Contact() { Name= "RicCC" , TotalOrderAmt=2080, EMail= "RicCC-test@gmail.com" , Address= "上海徐汇区钦州路" } }; this .items1 = new string [] { "AAA" , "BBB" , "CCC" }; this .items2 = new List<DateTime> { new DateTime(1979, 1, 1), new DateTime(2010, 4, 1) }; this .items3 = new Dictionary< string , decimal >(); 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; } } |
标签:
Castle
, Template Engine
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
2008-04-03 伸缩性思考1: SAP