Skip to content

Commit aa33223

Browse files
author
Igor Polevoy
committed
#216 Improve templator performance
1 parent d3a93f3 commit aa33223

File tree

5 files changed

+167
-66
lines changed

5 files changed

+167
-66
lines changed

activeweb/src/main/java/org/javalite/activeweb/templator/TemplatorManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class TemplatorManager implements TemplateManager {
2626
public void merge(Map input, String template, String layout, String format, Writer writer) {
2727

2828
String templateName = blank(format) ? template + ".html" : template + "." + format + ".html";
29-
Template pageTemplate = new Template(TemplatorConfig.instance().getTemplateSource(templateName));
29+
Template pageTemplate = TemplatorConfig.instance().getTemplate(templateName);
3030

3131
if (layout == null) {//no layout
3232
pageTemplate.process(input, writer);
@@ -37,7 +37,7 @@ public void merge(Map input, String template, String layout, String format, Writ
3737
Map values = new HashMap(input);
3838
values.put("page_content", pageWriter.toString());
3939

40-
Template layoutTemplate = new Template(TemplatorConfig.instance().getTemplateSource(layout + ".html"));
40+
Template layoutTemplate = TemplatorConfig.instance().getTemplate(layout + ".html");
4141
layoutTemplate.process(values, writer);
4242
logger.info("Rendered template: '" + template + "' with layout: '" + layout + "'");
4343
}

javalite-templator/pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@
3535
<artifactId>slf4j-simple</artifactId>
3636
</dependency>
3737

38-
<dependency>
39-
<groupId>net.sf.ehcache</groupId>
40-
<artifactId>ehcache-core</artifactId>
41-
</dependency>
4238

4339
</dependencies>
4440
</project>

javalite-templator/src/main/java/org/javalite/templator/AbstractTag.java

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.lang.reflect.Field;
99
import java.lang.reflect.InvocationTargetException;
1010
import java.lang.reflect.Method;
11+
import java.util.HashMap;
1112
import java.util.List;
1213
import java.util.Map;
1314

@@ -117,30 +118,30 @@ public boolean matchEndTag(String template, int index) {
117118
List<String> ends = getEnds();
118119
for (String end : ends) {
119120
boolean haveEnoughSpaceForEndTag = end.length() < index;
120-
boolean endTagMatchesLeftOfIndex = template.substring(index - end.length(), index).equals(end);
121+
boolean endTagMatchesLeftOfIndex = template.substring(index - end.length(), index).equals(end);
121122
boolean indexOnEnd = index == template.length() - 1;
122123
boolean endTagMatchesEndOfTemplate = template.substring(index - end.length() + 1).equals(end);
123124

124-
if (haveEnoughSpaceForEndTag && endTagMatchesLeftOfIndex){
125+
if (haveEnoughSpaceForEndTag && endTagMatchesLeftOfIndex) {
125126
matchingEnd = end;
126127
tagEndIndex = index - end.length();
127128
return true;
128129
}
129130

130-
if(indexOnEnd && endTagMatchesEndOfTemplate){
131-
matchingEnd = end;
131+
if (indexOnEnd && endTagMatchesEndOfTemplate) {
132+
matchingEnd = end;
132133
tagEndIndex = index - end.length() + 1;
133134
}
134135

135-
if(matchingEnd != null){
136+
if (matchingEnd != null) {
136137
return true;
137138
}
138139
}
139140
return false;
140141
}
141142

142143
public boolean matchStartAtIndex(String template, int index) {
143-
return index >= getTagStart().length()
144+
return index >= getTagStart().length()
144145
&& template.substring(index - getTagStart().length(), index).equals(getTagStart());
145146
}
146147

@@ -166,26 +167,35 @@ public String toString() {
166167
'}';
167168
}
168169

169-
public boolean marchArgumentEnd(String template, int index){
170+
public boolean marchArgumentEnd(String template, int index) {
170171
String argumentEnd = getArgumentEnd();
171-
if( argumentEnd == null){
172+
if (argumentEnd == null) {
172173
return false;
173-
}else if((index + 1) >= argumentEnd.length()
174-
&& template.substring(index - argumentEnd.length(), index).equals(argumentEnd) ){
174+
} else if ((index + 1) >= argumentEnd.length()
175+
&& template.substring(index - argumentEnd.length(), index).equals(argumentEnd)) {
175176
argumentsEndIndex = index - argumentEnd.length();
176177
return true;
177178
}
178179
return false;
179180
}
180181

182+
183+
private ThreadLocal<Map<String, Method>> methodCache = new ThreadLocal<Map<String, Method>>();
184+
181185
/**
182186
* Tries to get a property value from object.
183187
*
184-
* @param obj object to get property value from
188+
* @param obj object to get property value from
185189
* @param propertyName name of property
186190
* @return value of property, or null if not found
187191
*/
188-
protected final Object getValue(Object obj, String propertyName) throws InvocationTargetException, IllegalAccessException {
192+
protected final Object getValue(Object obj, String propertyName) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
193+
194+
195+
//quick hack:
196+
if (methodCache.get() == null) {
197+
methodCache.set(new HashMap<String, Method>());
198+
}
189199

190200
Object val = null;
191201

@@ -195,27 +205,22 @@ protected final Object getValue(Object obj, String propertyName) throws Invocati
195205
val = objectMap.get(propertyName);
196206
}
197207

198-
//try generic get method
199208
if (val == null) {
200-
try {
201-
Method m = obj.getClass().getMethod("get", String.class);
202-
val = m.invoke(obj, propertyName);
203-
} catch (NoSuchMethodException ignore) {
204-
}
209+
//try properties
210+
val = executeMethod(obj, "get" + capitalize(propertyName), null);
205211
}
206212

213+
214+
//try generic get method
207215
if (val == null) {
208-
//try properties
209-
try {
210-
Method m = obj.getClass().getMethod("get" + capitalize(propertyName));
211-
val = m.invoke(obj);
212-
} catch (NoSuchMethodException ignore) {
213-
}
216+
val = executeMethod(obj, "get", propertyName);
214217
}
215218

219+
216220
if (val == null) {
217221
// try public fields
218222
try {
223+
//TODO: optimize the same as methods.
219224
Field f = obj.getClass().getDeclaredField(propertyName);
220225
val = f.get(obj);
221226

@@ -227,7 +232,32 @@ protected final Object getValue(Object obj, String propertyName) throws Invocati
227232
return val;
228233
}
229234

230-
public boolean matchMiddle(String template, int templateIndex){
235+
private Object executeMethod(Object obj, String methodName, String propertyName) throws InvocationTargetException, IllegalAccessException {
236+
237+
String key = obj.getClass().getName() + "#" + methodName;
238+
Method m = null;
239+
240+
if (!methodCache.get().containsKey(key)) {
241+
try {
242+
m = propertyName == null ? obj.getClass().getMethod(methodName) : obj.getClass().getMethod(methodName, String.class);
243+
} catch (NoSuchMethodException e) {}
244+
245+
// if we find a method, we will cache it, if not we will cache null
246+
methodCache.get().put(key, m);
247+
} else if (methodCache.get().get(key) == null) { // we did not find this method last time!
248+
return null;
249+
} else {
250+
m = methodCache.get().get(key); // method found!
251+
}
252+
253+
if(m != null){
254+
return propertyName == null ? m.invoke(obj) : m.invoke(obj, propertyName);
255+
}else{
256+
return null;
257+
}
258+
}
259+
260+
public boolean matchMiddle(String template, int templateIndex) {
231261
return false;
232262
}
233263
}

javalite-templator/src/main/java/org/javalite/templator/TemplatorConfig.java

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package org.javalite.templator;
22

3-
import net.sf.ehcache.Cache;
4-
import net.sf.ehcache.CacheManager;
5-
import net.sf.ehcache.Element;
63
import org.javalite.common.Util;
74
import org.javalite.templator.tags.IfTag;
85
import org.javalite.templator.tags.ListTag;
@@ -30,8 +27,8 @@ private TemplatorConfig(){
3027
registerBuiltIn("esc", new Esc());
3128
}
3229

33-
private final static String CACHE_GROUP = "templates";
34-
private final CacheManager cacheManager = CacheManager.create();
30+
31+
private Map<String, Template> cacheManager = new HashMap<String, Template>();
3532
private final Map<String, Class> tags = new HashMap<String, Class>();
3633
private final Map<String, BuiltIn> builtIns = new HashMap<String, BuiltIn>();
3734
private boolean cacheTemplates = !(blank(System.getenv("ACTIVE_ENV")) || "development".equals(System.getenv("ACTIVE_ENV")));
@@ -100,19 +97,19 @@ public AbstractTag getTag(String name) {
10097
}
10198
}
10299

103-
public String getTemplateSource(String templateName) {
104-
String templateSource;
100+
public Template getTemplate(String templateName) {
101+
Template template;
105102
if(cacheTemplates){
106-
templateSource = getCache(templateName);
107-
if(templateSource != null){
108-
return templateSource;
103+
template = getCache(templateName);
104+
if(template != null){
105+
return template;
109106
}else{
110-
templateSource = loadTemplate(templateName);
111-
addCache(templateName, templateSource);
112-
return templateSource;
107+
template = new Template(loadTemplate(templateName));
108+
addCache(templateName, template);
109+
return template;
113110
}
114111
}else{
115-
return loadTemplate(templateName);
112+
return new Template(loadTemplate(templateName));
116113
}
117114
}
118115

@@ -164,31 +161,15 @@ public void setServletContext(ServletContext ctx) {
164161
this.servletContext = ctx;
165162
}
166163

167-
168-
public String getCache(String key) {
169-
try {
170-
createIfMissing();
171-
Cache c = cacheManager.getCache(CACHE_GROUP);
172-
return c.get(key) == null ? null : c.get(key).getObjectValue().toString();
173-
} catch (Exception e) {return null;}
164+
public Template getCache(String key) {
165+
return cacheManager.get(key) == null ? null : cacheManager.get(key);
174166
}
175167

176-
177-
public void addCache(String key, Object cache) {
178-
createIfMissing();
179-
cacheManager.getCache(CACHE_GROUP).put(new Element(key, cache));
168+
public void addCache(String key, Template template) {
169+
cacheManager.put(key, template);
180170
}
181171

182172
public void flush() {
183-
cacheManager.removalAll();
184-
}
185-
186-
private void createIfMissing() {
187-
//double-checked synchronization is broken in Java, but this should work just fine.
188-
if (cacheManager.getCache(CACHE_GROUP) == null) {
189-
try {
190-
cacheManager.addCache(CACHE_GROUP);
191-
} catch (net.sf.ehcache.ObjectExistsException ignore) {}
192-
}
173+
cacheManager = new HashMap<String, Template>();
193174
}
194175
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package org.javalite.templator;
2+
3+
import org.junit.Before;
4+
import org.junit.Test;
5+
6+
import java.io.IOException;
7+
import java.io.StringWriter;
8+
import java.util.ArrayList;
9+
import java.util.List;
10+
11+
import static org.javalite.common.Collections.map;
12+
13+
/**
14+
* This is not a real spec. This is a performance test to see which templating engine is faster,
15+
* hand-written or generated from BNF. Looks like hand-written is 3 - 5 times faster.
16+
*
17+
* @author Igor Polevoy on 4/15/15.
18+
*/
19+
public class CompareSpec {
20+
21+
public static class Person {
22+
private String firstName, lastName;
23+
24+
public Person(String firstName, String lastName) {
25+
this.firstName = firstName;
26+
this.lastName = lastName;
27+
}
28+
29+
public String getFirstName() {
30+
return firstName;
31+
}
32+
33+
public String getLastName() {
34+
return lastName;
35+
}
36+
}
37+
38+
private List<Person> people;
39+
private long millis;
40+
41+
42+
@Before
43+
public void before() {
44+
Runtime.getRuntime().gc();
45+
people = new ArrayList<Person>();
46+
for (int i = 0; i < 10000; i++) {
47+
people.add(new Person("first-" + i, "last-" + i));
48+
}
49+
}
50+
51+
52+
class NopeStringWriter extends StringWriter{
53+
@Override
54+
public void write(int c) {}
55+
@Override
56+
public void write(char[] cbuf, int off, int len) {}
57+
@Override
58+
public void write(String str) {}
59+
@Override
60+
public void write(String str, int off, int len) {}
61+
@Override
62+
public String toString() {return "";}
63+
@Override
64+
public StringWriter append(CharSequence csq) {return this;}
65+
@Override
66+
public StringWriter append(CharSequence csq, int start, int end) {return this;}
67+
@Override
68+
public StringWriter append(char c) {return this;}
69+
}
70+
71+
@Test
72+
public void shouldIterate1() {
73+
String source = "<html><#list people as person > name: ${person.firstName} ${person.lastName} </#list></html>";
74+
Template template = new Template(source);
75+
StringWriter w = new NopeStringWriter();
76+
millis = System.currentTimeMillis();
77+
for (int i = 0; i < 100; i++) {
78+
template.process(map("people", people), w);
79+
}
80+
System.out.println("Completed in " + (System.currentTimeMillis() - millis) + " milliseconds");
81+
}
82+
83+
84+
@Test
85+
public void shouldIterate2() throws IOException {
86+
TemplateTagNode node = (TemplateTagNode) new TemplateParser("<html><#for person:people> name: %{person.firstName} %{person.lastName} </#for></html>").parse();
87+
StringWriter sw = new NopeStringWriter();
88+
millis = System.currentTimeMillis();
89+
for (int i = 0; i < 100; i++) {
90+
node.process(map("people", people), sw);
91+
}
92+
System.out.println("Completed in " + (System.currentTimeMillis() - millis) + " milliseconds");
93+
}
94+
}

0 commit comments

Comments
 (0)