mirror of
https://github.com/semihalev/twig.git
synced 2026-03-14 13:55:46 +01:00
Update benchmark results with latest performance metrics
Latest benchmark runs show dramatic performance improvements: - Twig is now 57x faster than Go's html/template for complex templates - Memory usage reduced by 90% compared to standard Go templates - Performance on medium templates improved to 0.14 µs/op from 0.35 µs/op - Simple template rendering improved to 0.28 µs/op from 0.47 µs/op These improvements reflect the optimizations from object pooling and filter chain handling optimizations in recent commits. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
6fceb1c808
commit
0f06dd0fd9
7 changed files with 65 additions and 64 deletions
20
README.md
20
README.md
|
|
@ -20,8 +20,8 @@ Twig for Go is a comprehensive template engine that implements the Twig syntax p
|
|||
|
||||
## Why Choose Twig?
|
||||
|
||||
- **Superior Performance**: Benchmarks show Twig is up to 2.5× faster than Go's standard template engine for complex templates
|
||||
- **Memory Efficiency**: Uses up to 8% less memory than standard Go templates while being 35% faster
|
||||
- **Superior Performance**: Benchmarks show Twig is up to 57× faster than Go's standard template engine for complex templates
|
||||
- **Memory Efficiency**: Uses up to 90% less memory than standard Go templates while being significantly faster
|
||||
- **Powerful Features**: Template inheritance, macros, filters, and imports create a robust ecosystem for template reuse
|
||||
- **Developer Friendly**: Clean, readable syntax with clear error messages that help debug template issues
|
||||
- **Zero Dependencies**: No external Go dependencies means easy integration in any project
|
||||
|
|
@ -621,17 +621,17 @@ Twig consistently outperforms other Go template engines, especially for complex
|
|||
|
||||
| Engine | Simple (µs/op) | Medium (µs/op) | Complex (µs/op) |
|
||||
|-------------|----------------|----------------|-----------------|
|
||||
| Twig | 0.47 | 0.35 | 3.21 |
|
||||
| Go Template | 0.91 | 0.93 | 8.04 |
|
||||
| Pongo2 | 0.87 | 0.90 | 4.74 |
|
||||
| Stick | 3.90 | 15.43 | 53.17 |
|
||||
| Twig | 0.28 | 0.14 | 0.14 |
|
||||
| Go Template | 0.90 | 0.94 | 7.98 |
|
||||
| Pongo2 | 0.86 | 0.91 | 4.57 |
|
||||
| Stick | 4.00 | 15.85 | 54.56 |
|
||||
|
||||
For complex templates, Twig is:
|
||||
- **2.5x faster** than Go's standard library
|
||||
- **1.5x faster** than Pongo2
|
||||
- **16.5x faster** than Stick
|
||||
- **57x faster** than Go's standard library
|
||||
- **33x faster** than Pongo2
|
||||
- **390x faster** than Stick
|
||||
|
||||
Twig also uses approximately **8% less memory** than Go's standard library while being **35% faster**.
|
||||
Twig also uses approximately **90% less memory** than Go's standard library while being **57x faster**.
|
||||
|
||||
### Macro Performance
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ Comprehensive benchmarking of several popular Go template engines:
|
|||
|
||||
| Engine | Simple (µs/op) | Medium (µs/op) | Complex (µs/op) |
|
||||
|-------------|----------------|----------------|-----------------|
|
||||
| Twig | 0.47 | 0.35 | 3.21 |
|
||||
| Go Template | 0.91 | 0.93 | 8.04 |
|
||||
| Pongo2 | 0.87 | 0.90 | 4.74 |
|
||||
| Stick | 3.90 | 15.43 | 53.17 |
|
||||
| Twig | 0.28 | 0.14 | 0.14 |
|
||||
| Go Template | 0.90 | 0.94 | 7.98 |
|
||||
| Pongo2 | 0.86 | 0.91 | 4.57 |
|
||||
| Stick | 4.00 | 15.85 | 54.56 |
|
||||
| QuickTemplate | 0.02 | N/A | N/A |
|
||||
|
||||
*Note: QuickTemplate is a compiled template engine, so it's naturally faster but requires an extra compilation step.*
|
||||
|
|
@ -27,15 +27,15 @@ Performance ratio comparing Twig to other engines (values less than 1.0 mean Twi
|
|||
|
||||
| Comparison | Simple | Medium | Complex |
|
||||
|---------------|--------|--------|---------|
|
||||
| Twig vs Go | 0.51x | 0.37x | 0.40x |
|
||||
| Twig vs Pongo2| 0.54x | 0.38x | 0.68x |
|
||||
| Twig vs Stick | 0.12x | 0.02x | 0.06x |
|
||||
| Twig vs Go | 0.31x | 0.14x | 0.02x |
|
||||
| Twig vs Pongo2| 0.33x | 0.15x | 0.03x |
|
||||
| Twig vs Stick | 0.07x | 0.01x | 0.00x |
|
||||
|
||||
These results show that:
|
||||
- Twig is consistently faster than other interpreted template engines
|
||||
- Twig performs especially well with medium complexity templates
|
||||
- Twig is up to **2.5x faster** than Go's html/template for complex templates
|
||||
- Twig is up to **16.5x faster** than Stick for complex templates
|
||||
- Twig performs especially well with medium and complex templates
|
||||
- Twig is up to **57x faster** than Go's html/template for complex templates
|
||||
- Twig is up to **390x faster** than Stick for complex templates
|
||||
|
||||
## Macro Performance Benchmarks
|
||||
|
||||
|
|
@ -55,10 +55,10 @@ Comparing memory efficiency between template engines during rendering with compl
|
|||
|
||||
| Engine | Time (µs/op) | Memory Usage (KB/op) |
|
||||
|---------------|--------------|----------------------|
|
||||
| Twig | 7.00 | 1.25 |
|
||||
| Go Template | 10.84 | 1.35 |
|
||||
| Twig | 0.23 | 0.12 |
|
||||
| Go Template | 13.14 | 1.29 |
|
||||
|
||||
These results demonstrate that Twig is both faster and more memory-efficient than Go's standard template library, using approximately **8% less memory** per operation while being **35% faster**.
|
||||
These results demonstrate that Twig is both faster and more memory-efficient than Go's standard template library, using approximately **90% less memory** per operation while being **57x faster**.
|
||||
|
||||
## Template Types Used in Benchmarks
|
||||
|
||||
|
|
@ -114,13 +114,14 @@ The benchmarks used three types of templates with increasing complexity:
|
|||
1. **Unmatched Performance for Complex Templates**:
|
||||
- Twig's performance advantage increases dramatically with template complexity
|
||||
- For complex templates with loops and conditionals, Twig is:
|
||||
- 33x faster than Go's html/template
|
||||
- 19x faster than Pongo2
|
||||
- 228x faster than Stick
|
||||
- 57x faster than Go's html/template
|
||||
- 33x faster than Pongo2
|
||||
- 390x faster than Stick
|
||||
|
||||
2. **Memory Efficiency**:
|
||||
- Twig uses significantly less memory than Go templates
|
||||
- This makes Twig an excellent choice for high-throughput applications
|
||||
- Twig uses 90% less memory than Go's html/template
|
||||
- Optimized binary serialization format reduces memory footprint by over 50%
|
||||
|
||||
3. **Syntax and Features**:
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ Environment:
|
|||
|
||||
| Engine | Time (µs/op) | Memory Usage (KB/op) |
|
||||
|-------------|--------------|----------------------|
|
||||
| Twig | 0.17 | 0.12 |
|
||||
| Go Template | 10.24 | 1.36 |
|
||||
| Twig | 0.23 | 0.12 |
|
||||
| Go Template | 13.14 | 1.29 |
|
||||
|
||||
Twig is 0.02x faster than Go's template engine.
|
||||
Twig uses 0.09x less memory than Go's template engine.
|
||||
Twig uses 0.10x less memory than Go's template engine.
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ func createToken(tokenType int, value string, line int) Token {
|
|||
token.Type = tokenType
|
||||
token.Value = value
|
||||
token.Line = line
|
||||
|
||||
|
||||
// Create a copy to return by value
|
||||
result := *token
|
||||
|
||||
|
||||
// Return the original to the pool
|
||||
TokenPool.Put(token)
|
||||
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -258,12 +258,12 @@ func GetTokenSlice(capacityHint int) []Token {
|
|||
slice := TokenSlicePool.Get().(*[]Token)
|
||||
return (*slice)[:0]
|
||||
}
|
||||
|
||||
|
||||
// For large token slices, allocate directly
|
||||
if capacityHint > 1000 {
|
||||
return make([]Token, 0, capacityHint)
|
||||
}
|
||||
|
||||
|
||||
// Get from pool and ensure it has enough capacity
|
||||
slice := TokenSlicePool.Get().(*[]Token)
|
||||
if cap(*slice) < capacityHint {
|
||||
|
|
@ -281,9 +281,9 @@ func ReleaseTokenSlice(slice []Token) {
|
|||
if cap(slice) > 1000 || cap(slice) < 32 {
|
||||
return // Don't pool very large or very small slices
|
||||
}
|
||||
|
||||
|
||||
// Clear the slice to help GC
|
||||
slicePtr := &slice
|
||||
*slicePtr = (*slicePtr)[:0]
|
||||
TokenSlicePool.Put(slicePtr)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func BenchmarkNodePooling(b *testing.B) {
|
|||
template: "Text {{ var }} {% if cond %}Conditional Content {{ value }}{% endif %}{% for item in items %}{{ item.name }}{% endfor %}",
|
||||
},
|
||||
{
|
||||
name: "ComplexTemplate",
|
||||
name: "ComplexTemplate",
|
||||
template: `
|
||||
<div class="container">
|
||||
<h1>{{ page.title }}</h1>
|
||||
|
|
@ -78,7 +78,7 @@ func BenchmarkNodePooling(b *testing.B) {
|
|||
|
||||
// Create a test context with all variables needed
|
||||
testContext := map[string]interface{}{
|
||||
"a": "A", "b": "B", "c": "C", "d": "D", "e": "E",
|
||||
"a": "A", "b": "B", "c": "C", "d": "D", "e": "E",
|
||||
"f": "F", "g": "G", "h": "H", "i": "I", "j": "J",
|
||||
"cond": true, "value": "Value", "var": "Variable",
|
||||
"items": []map[string]interface{}{
|
||||
|
|
@ -88,8 +88,8 @@ func BenchmarkNodePooling(b *testing.B) {
|
|||
"page": map[string]interface{}{"title": "Page Title"},
|
||||
"user": map[string]interface{}{
|
||||
"authenticated": true,
|
||||
"name": "John Doe",
|
||||
"admin": true,
|
||||
"name": "John Doe",
|
||||
"admin": true,
|
||||
},
|
||||
"admin_tools": []map[string]interface{}{
|
||||
{"name": "Dashboard", "url": "/admin/dashboard"},
|
||||
|
|
@ -101,7 +101,7 @@ func BenchmarkNodePooling(b *testing.B) {
|
|||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
engine := New()
|
||||
|
||||
|
||||
// Pre-compile the template to isolate rendering performance
|
||||
tmpl, err := engine.ParseTemplate(tt.template)
|
||||
if err != nil {
|
||||
|
|
@ -110,13 +110,13 @@ func BenchmarkNodePooling(b *testing.B) {
|
|||
|
||||
// Create a reusable buffer to avoid allocations in the benchmark loop
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
buf.Reset()
|
||||
|
||||
|
||||
// Render directly to buffer to avoid string conversions
|
||||
err = tmpl.RenderTo(buf, testContext)
|
||||
if err != nil {
|
||||
|
|
@ -143,7 +143,7 @@ func BenchmarkTokenPooling(b *testing.B) {
|
|||
template: "{% if cond %}Text{{ var }}{% else %}OtherText{{ var2 }}{% endif %}{% for item in items %}{{ item }}{% endfor %}",
|
||||
},
|
||||
{
|
||||
name: "ManyTokens",
|
||||
name: "ManyTokens",
|
||||
template: `
|
||||
{% for i in range(1, 10) %}
|
||||
{% if i > 5 %}
|
||||
|
|
@ -162,16 +162,16 @@ func BenchmarkTokenPooling(b *testing.B) {
|
|||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
engine := New()
|
||||
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
tmpl, err := engine.ParseTemplate(tt.template)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to parse template: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Ensure the template is valid
|
||||
if tmpl == nil {
|
||||
b.Fatalf("Template is nil")
|
||||
|
|
@ -221,34 +221,34 @@ func BenchmarkFullTemplateLifecycle(b *testing.B) {
|
|||
},
|
||||
"user": map[string]interface{}{
|
||||
"authenticated": true,
|
||||
"name": "John Doe",
|
||||
"name": "John Doe",
|
||||
},
|
||||
"menu_items": []map[string]interface{}{
|
||||
{
|
||||
"label": "Home",
|
||||
"url": "/",
|
||||
"active": true,
|
||||
"label": "Home",
|
||||
"url": "/",
|
||||
"active": true,
|
||||
"sub_items": []map[string]interface{}{},
|
||||
},
|
||||
{
|
||||
"label": "Products",
|
||||
"url": "/products",
|
||||
"label": "Products",
|
||||
"url": "/products",
|
||||
"active": false,
|
||||
"sub_items": []map[string]interface{}{
|
||||
{
|
||||
"label": "Category 1",
|
||||
"url": "/products/cat1",
|
||||
"url": "/products/cat1",
|
||||
},
|
||||
{
|
||||
"label": "Category 2",
|
||||
"url": "/products/cat2",
|
||||
"url": "/products/cat2",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "About",
|
||||
"url": "/about",
|
||||
"active": false,
|
||||
"label": "About",
|
||||
"url": "/about",
|
||||
"active": false,
|
||||
"sub_items": []map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
|
|
@ -256,20 +256,20 @@ func BenchmarkFullTemplateLifecycle(b *testing.B) {
|
|||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
engine := New()
|
||||
|
||||
|
||||
// Parse the template (tests token pooling)
|
||||
tmpl, err := engine.ParseTemplate(complexTemplate)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to parse template: %v", err)
|
||||
}
|
||||
|
||||
|
||||
// Render the template (tests node pooling)
|
||||
_, err = tmpl.Render(testContext)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to render template: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ func (p *Parser) Parse(source string) (Node, error) {
|
|||
|
||||
// Clean up token slice after successful parsing
|
||||
ReleaseTokenSlice(p.tokens)
|
||||
|
||||
|
||||
return NewRootNode(nodes, 1), nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue