Replace HAML with Hamlit
What this change does:
- It replaces HAML with Hamlit, a much more efficient implementation of HAML.
- It removes
haml-rails
. - It adds
hamlit.rb
and removeshaml.rb
. - It makes things faster and less memory-hungry!
I used the rack-mini-profiler
, flamegraph
, stackprof
, and memory_profiler
gems to do this testing and get the results shown below. Thanks to Nate Berkopec's fantastic post on Rack Mini Profiler.
These results are from rendering the route /connorshea/gitlab-ce
, which was imported from https://gitlab.com/gitlab-org/gitlab-ce.git
. The user is not logged in (because for whatever reason I can only be logged in on one of the locally-hosted GitLab instances at a time, despite using separate ports).
I set up two GDKs, ran them side-by-side under ports 3000 and 3001, one using the master branch (Before) and one using this branch (After). I then opened the above route in both. Note that both have the performance analysis gems I mentioned above, that's the only real difference between the actual master branch and the one tested.
Before:
allocated memory by gem
-----------------------------------
21737597 binding_of_caller-0.7.2
6515235 activesupport-4.2.6
5168664 sprockets-3.6.0
3247421 attr_encrypted-1.3.4
2947358 gitlab/lib
2503399 ruby-2.1.8/lib
2382604 nokogiri-1.6.7.2
2115132 activerecord-4.2.6
1031632 redis-3.2.2
899678 sanitize-2.1.0
668542 gitlab_git-10.0.0
549896 actionview-4.2.6
392643 haml-4.0.7
371232 actionpack-4.2.6
324777 gitlab/app
134824 rugged-0.24.0
91059 addressable-2.3.8
80576 better_errors-1.0.1
70416 html-pipeline-1.11.0
68685 default_value_for-3.0.1
48937 rouge-1.10.1
48616 arel-6.0.3
38248 warden-1.2.4
28372 i18n-0.7.0
25173 devise-3.5.4
24145 gon-6.0.1
23248 font-awesome-rails-4.5.0.1
22570 rack-1.6.4
11505 activemodel-4.2.6
10099 redis-store-1.1.7
9149 json-1.8.3
9143 task_list-1.0.2
5216 redis-activesupport-4.1.5
4917 sprockets-rails-3.0.4
3800 thread_safe-0.3.5
3360 six-0.2.0
2195 railties-4.2.6
2136 multi_json-1.11.2
2064 state_machines-0.4.0
1248 paranoia-2.1.4
794 browser-1.0.1
761 rack-attack-4.3.1
744 carrierwave-0.10.0
704 github-linguist-4.7.5
672 teaspoon-1.1.5
600 other
504 web-console-2.3.0
489 state_machines-activerecord-0.3.0
480 sentry-raven-0.15.6
256 tzinfo-1.2.2
After:
allocated memory by gem
-----------------------------------
21582740 binding_of_caller-0.7.2
6498356 activesupport-4.2.6
5269792 sprockets-3.6.0
3205405 attr_encrypted-1.3.4
2946893 gitlab/lib
2534290 ruby-2.1.8/lib
2383029 nokogiri-1.6.7.2
2115091 activerecord-4.2.6
1063457 redis-3.2.2
899678 sanitize-2.1.0
665599 gitlab_git-10.0.0
595189 actionview-4.2.6
371320 actionpack-4.2.6
243847 gitlab/app
134824 rugged-0.24.0
105876 haml-4.0.7
91059 addressable-2.3.8
81000 better_errors-1.0.1
70376 html-pipeline-1.11.0
68685 default_value_for-3.0.1
48937 rouge-1.10.1
48616 arel-6.0.3
38208 warden-1.2.4
28372 i18n-0.7.0
25173 devise-3.5.4
24145 gon-6.0.1
23248 font-awesome-rails-4.5.0.1
22570 rack-1.6.4
11712 redis-store-1.1.7
11225 activemodel-4.2.6
9149 json-1.8.3
9143 task_list-1.0.2
5216 redis-activesupport-4.1.5
4917 sprockets-rails-3.0.4
3360 six-0.2.0
3224 thread_safe-0.3.5
2195 railties-4.2.6
2136 multi_json-1.11.2
2064 state_machines-0.4.0
1248 paranoia-2.1.4
1172 hamlit-2.2.3
794 browser-1.0.1
761 rack-attack-4.3.1
744 carrierwave-0.10.0
704 github-linguist-4.7.5
672 teaspoon-1.1.5
600 other
560 redis-rack-1.5.0
504 web-console-2.3.0
489 state_machines-activerecord-0.3.0
As you can see, less memory is used by Hamlit.
before 392643 haml-4.0.7
-----------------------------
after 1172 hamlit-2.2.3
105876 haml-4.0.7
392643
before, 107048
after. That's a 72.7% decrease in the memory used by HAML! Note that the above stats can be accessed at /USERNAME/gitlab-ce?pp=profile-memory
if you have the rack-mini-profiler and memory_profiler gems installed.
So why is haml
still there? Brakeman requires haml
, and for whatever reason the haml
gem monkey-patches in ActionView#render_with_haml
causing the haml
gem to still technically "control" the end-result. The hamlit
gem actually rendering things, however, which is why there's such a difference in memory allocation.
What difference does this make for rendering views?
I don't have solid numbers, but based on rack-mini-profiler's data it seems like it's at least 150+ ms faster to render the gitlab-ce repository view, but that's based on a handful of tests, so I'll need to put together a benchmark with benchmark-ips
and follow up with the data from that.
Are there any issues that break things?
Maybe! Not sure yet. I know Hamlit doesn't implement any of HAML's helpers, so that could be a problem.
Will this break in a production environment?
There's a good chance it will, since I know we use the HAML preserve helper, which is not in Hamlit (See https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/helpers/application_helper.rb#L217). The reason it doesn't break right now in development is because Brakeman requires haml
, as far as I can tell.
See also #13201 (closed).