Richie

Sometimes at night when I look up at the stars, and see the whole sky just laid out there, don't you think I ain't remembering it all. I still got dreams like anybody else, and ever so often, I am thinking about how things might of been. And then, all of a sudden, I'm forty, fifty, sixty years old, you know?

NVelocity 1.1 vs StringTemplate 3.2

拿NVelocity 1.1、StringTemplate 3.2和ASP.NET的ASPX页面做了个性能测试对比,对比结果如下:
    NVelocity、StringTemplate与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)
测试用的项目文件: 下载

测试辅助类:
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>
row.st
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>
ASPNET.aspx.cs:
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;
    }
}

posted on   riccc  阅读(3855)  评论(7编辑  收藏  举报

编辑推荐:
· 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

导航

统计信息

点击右上角即可分享
微信分享提示