Skip to content

Commit b3e205d

Browse files
committed
Research for compiling to method
1 parent fbf1cfa commit b3e205d

File tree

4 files changed

+250
-5
lines changed

4 files changed

+250
-5
lines changed

TODO.md

Lines changed: 60 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,68 @@
11
## Immediate
22

3-
- [ ] - Fix bad compiled source generation ternary op:
3+
## Switch code generation to generating a method
44

5-
```ruby
6-
-> (p) {
7-
p ? a('<', href: p, class: 'prev') : span(class: 'prev')
5+
Benchmarks show a significant performance increase when instead of creating a
6+
compiled lambda, we create a method that is invoked directly. Take the following
7+
example:
8+
9+
```ruby
10+
t = ->(foo, bar) {
11+
div {
12+
h1 foo
13+
h2 bar
814
}
9-
```
15+
}
16+
```
17+
18+
The compiled form currently is:
19+
20+
```ruby
21+
->(__buffer__, foo, bar) {
22+
__buffer__
23+
.<<("<div><h1>")
24+
.<<(ERB::Escape.html_escape((foo)))
25+
.<<("</h1><h2>")
26+
.<<(ERB::Escape.html_escape((bar)))
27+
.<<("</h2></div>")
28+
__buffer__
29+
}
30+
```
31+
32+
Instead, we can define a method on the template itself:
1033

34+
```ruby
35+
def t.__papercraft_render_html(__buffer__, foo, bar)
36+
__buffer__
37+
.<<("<div><h1>")
38+
.<<(ERB::Escape.html_escape((foo)))
39+
.<<("</h1><h2>")
40+
.<<(ERB::Escape.html_escape((bar)))
41+
.<<("</h2></div>")
42+
__buffer__
43+
end
44+
```
45+
46+
And then, we change `Papercraft.html` to do:
47+
48+
```ruby
49+
def Papercraft.html(template, *, **, &)
50+
template.__papercraft_render_html(+'', *, **, &)
51+
rescue => e
52+
translate_backtrace(e)
53+
end
54+
```
55+
56+
The default impl for `__papercraft_render_html` can be:
57+
58+
```ruby
59+
class ::Proc
60+
def __papercraft_render_html(*, **, &)
61+
Papercraft.compile_render_html_method(self)
62+
__papercraft_render_html(*, **, &)
63+
end
64+
end
65+
```
1166

1267
## Support for inlining
1368

examples/perf_instance_eval.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# frozen_string_literal: true
2+
3+
require 'benchmark/ips'
4+
5+
pr = proc {}
6+
7+
Benchmark.ips do |x|
8+
x.report("call") { pr.call }
9+
x.report("i_eval") { instance_eval(&pr) }
10+
11+
x.compare!(order: :baseline)
12+
end

examples/perf_optimized.rb

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# frozen_string_literal: true
2+
3+
require 'bundler/inline'
4+
5+
gemfile do
6+
gem 'papercraft', path: '.'
7+
gem 'benchmark'
8+
gem 'benchmark-ips', '>= 2.14.0'
9+
end
10+
11+
require 'papercraft/proc'
12+
require 'erb'
13+
require 'benchmark/ips'
14+
15+
TemplatePapercraft = ->(foo, bar) {
16+
div {
17+
h1 foo
18+
h2 bar
19+
}
20+
}
21+
22+
TemplateERB = <<~ERB
23+
<div>
24+
<h1><%= ERB::Escape.html_escape(foo) %></h1>
25+
<p><%= ERB::Escape.html_escape(bar) %></p>
26+
</div>
27+
ERB
28+
29+
def render_papercraft(foo, bar)
30+
TemplatePapercraft.__papercraft_compiled_proc.(+'', foo, bar)
31+
end
32+
33+
def render_papercraft_optimized(foo, bar)
34+
TemplatePapercraft.__optimized_html(+'', foo, bar)
35+
rescue => e
36+
p e
37+
end
38+
39+
puts
40+
TemplatePapercraft.singleton_class.class_eval <<~RUBY.tap { puts it }
41+
# frozen_string_literal: true
42+
def __optimized_html(__buffer__, foo, bar)
43+
__buffer__.<<("<div><h1>").<<(
44+
ERB::Escape.html_escape((foo))).<<("</h1><h2>").<<(
45+
ERB::Escape.html_escape((bar))).<<("</h2></div>")
46+
__buffer__
47+
end
48+
RUBY
49+
puts
50+
51+
puts
52+
singleton_class.class_eval <<~RUBY.tap { puts it }
53+
# frozen_string_literal: true
54+
def render_optimized(foo, bar)
55+
__buffer__ = +''
56+
__buffer__.<<("<div><h1>").<<(
57+
ERB::Escape.html_escape((foo))).<<("</h1><h2>").<<(
58+
ERB::Escape.html_escape((bar))).<<("</h2></div>")
59+
__buffer__
60+
end
61+
RUBY
62+
puts
63+
64+
puts
65+
singleton_class.class_eval <<~RUBY.tap { puts it }
66+
# frozen_string_literal: true
67+
def render_erb(foo, bar)
68+
#{ERB.new(TemplateERB).src}
69+
rescue => e
70+
raise e
71+
end
72+
RUBY
73+
puts
74+
75+
def render_erb_indirect(foo, bar)
76+
render_erb(foo, bar)
77+
end
78+
79+
puts
80+
puts TemplatePapercraft.compiled_code
81+
puts
82+
83+
puts render_erb('FOO', 'BAR')
84+
puts render_papercraft('FOO', 'BAR')
85+
puts render_optimized('FOO', 'BAR')
86+
puts render_papercraft_optimized('FOO', 'BAR')
87+
puts
88+
89+
# puts '* ERB:'
90+
# puts r.render_erb_app.gsub(/\n\s+/, '')
91+
92+
# puts '* ERUBI (raw):'
93+
# puts r.render_erubi_app.gsub(/\n\s+/, '')
94+
95+
res = Benchmark.ips do |x|
96+
# x.config(:time => 5, :warmup => 2)
97+
98+
x.report("erb") { render_erb('FOO', 'BAR') }
99+
x.report("erb_indirect") { render_erb_indirect('FOO', 'BAR') }
100+
x.report("papercraft") { render_papercraft('FOO', 'BAR') }
101+
x.report("papercraft_optimized") { render_papercraft_optimized('FOO', 'BAR') }
102+
x.report("optimized") { render_optimized('FOO', 'BAR') }
103+
104+
x.compare!(order: :baseline)
105+
end
106+
107+
108+
p res

examples/perf_raw.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# frozen_string_literal: true
2+
3+
require 'erb'
4+
require 'benchmark/ips'
5+
6+
TEMPLATE_ERB = <<~HTML
7+
<div>
8+
<h1><%= 'foo' %></h1>
9+
<p><%= 'bar' %></p>
10+
</div>
11+
HTML
12+
13+
TEMPLATE_DSL = proc {
14+
div {
15+
h1 'foo'
16+
p 'bar'
17+
}
18+
}
19+
20+
class Renderer
21+
def render_dsl(template)
22+
@buffer = +''
23+
instance_eval(&template)
24+
@buffer
25+
end
26+
27+
def div(&block)
28+
@buffer << '<div>'
29+
instance_eval(&block)
30+
@buffer << '</div>'
31+
end
32+
33+
def h1(text)
34+
@buffer << "<h1>#{text}</h1>"
35+
end
36+
37+
def p(text)
38+
@buffer << "<p>#{text}</p>"
39+
end
40+
41+
class_eval(c = <<~RUBY)
42+
# frozen_string_literal: true
43+
def render_erb
44+
#{ERB.new(TEMPLATE_ERB).src}
45+
end
46+
RUBY
47+
end
48+
49+
r = Renderer.new
50+
51+
puts '* ERB:'
52+
puts r.render_erb
53+
puts
54+
55+
puts '* DSL:'
56+
puts r.render_dsl(TEMPLATE_DSL)
57+
puts
58+
59+
puts
60+
puts ERB.new(TEMPLATE_ERB).src
61+
puts
62+
63+
Benchmark.ips do |x|
64+
# x.config(:time => 5, :warmup => 2)
65+
66+
x.report("ERB") { r.render_erb }
67+
x.report("DSL") { r.render_dsl(TEMPLATE_DSL) }
68+
69+
x.compare!(order: :baseline)
70+
end

0 commit comments

Comments
 (0)