tagz vs markaby/builder/haml/erubis

| | Comments (13)

I've been meaning to write and run this benchmark for a while to see how things fare in the view world these days. Initially I did markaby vs tagz (my favorite at a svelte 244 lines). I then added builder out of curiosity's sake. Thanks to atmos I was able to get code and numbers for erubis and haml.

Builder is slightly faster than tagz, but not enough for me to want to switch to it. Tagz and markaby read so much better than builder, but markaby is so much slower (4.73 times slower than builder) that I can't justify its use.

Now if only I could get tagz working correctly with sinatra. :(

# iterations = 10000
#
#               user     system      total        real   multiplier
#
# builder   1.910000   0.000000   1.910000 (  1.918751) (1.00)
# tagz      2.310000   0.020000   2.330000 (  2.357710) (1.23)
# haml      5.370000   0.020000   5.390000 (  5.418876) (2.82)
# erubis    5.390000   0.020000   5.410000 (  5.446929) (2.84)
# markaby   8.980000   0.030000   9.010000 (  9.078748) (4.73)

Code behind the cut...

Code:

#!/usr/bin/env ruby

require 'benchmark'
require 'rubygems'
require 'markaby'
require 'tagz'

max = (ARGV.shift || 1_000_000).to_i

def do_nothing
  # do nothing
end

require 'haml'
require 'erubis'

def do_erubis
  str = <<-EOF
<html>
  <head>
    <title>happy title</title>
  </head>
  <body>
    <h1>happy heading</h1>
    <a href='<%= 'url' %>'>a link</a>
  </body>
</html>
EOF
  eruby = Erubis::Eruby.new(str)
end

def do_haml
  str = <<-EOF
%html
  %head
    %title_ "happy title"
  %body
    %h1 "happy heading"
    %a "a link", :href => "url"
EOF
  Haml::Engine.new(str)
end

def do_tagz
  Tagz {
    html_ {
      head_ {
        title_ "happy title"
      }
      body_ {
        h1_ "happy heading"
        a_ "a link", :href => "url"
      }
    }
  }
end

def do_builder
  Builder::XmlMarkup.new.html {
    xm.head {
      xm.title "happy title"
    }
    xm.body {
      xm.h1 "happy heading"
      xm.a "a link", :href => "url"
    }
  }
end

def do_markaby
  mab = Markaby::Builder.new :output_meta_tag => false

  mab.html {
    head {
      title "happy title"
    }
    body {
      h1 "happy heading"
      a "a link", :href => "url"
    }
  }.to_s
end

x = do_tagz
y = do_markaby
z = do_builder

raise "bad!\n\n#{x}\n\n#{y}" unless x == y
raise "bad!\n\n#{x}\n\n#{z}" unless x == z

puts "# of iterations = #{max}"
Benchmark::bm(20) do |x|
  x.report("null_time") do
    for i in 0..max do
      do_nothing
    end
  end

  x.report("tagz") do
    for i in 0..max do
      do_tagz
    end
  end

  x.report("markaby") do
    for i in 0..max do
      do_markaby
    end
  end

  x.report("builder") do
    for i in 0..max do
      do_builder
    end
  end
end

13 Comments

So I guess you’re not aware of Erector? Using this function for testing:

def do_erector
  Erector::Widget.new do
    html {
      head {
        title "happy title"
      }
      body {
        h1 "happy heading"
        a "a link", :href => "url"
      }
    }
  end.to_s
end

gave me these results:

# of iterations = 10000
                          user     system      total        real
null_time             0.000000   0.000000   0.000000 (  0.005470)
tagz                  2.490000   0.060000   2.550000 (  2.620850)
markaby               9.520000   0.190000   9.710000 ( 10.032617)
erector               1.470000   0.020000   1.490000 (  1.524160)

nope. I wasn't aware of erector. But there isn't a chance in the world that I'd ever pick it over tagz:

% find lib -name \*.rb | xargs wc -l | tail -1
   19525 total

I think you should double-check your numbers... there's no way Erubis would be so slow. On my machine (macbook, 1.83Ghz) it takes 0.67 seconds to perform 10000 iterations (compared to ~7s for haml and ~13s for markaby).

I'm curious what happened that erubis was so slow for you - it's far and away the fastest of the bunch on my machine:

http://gist.github.com/65299

(and that's after I fixed the erubis code to actually render the html string, as well)

This isn't really a fair comparison. Haml and Erubis both take a significant amount of time just to parse their templates. In a real-world situation, the compiled Ruby code they generate would be cached. I've made a fixed benchmark here: http://gist.github.com/65305. It also fixes a couple errors in the Haml syntax.

Again, if you read my post, you'll see that I did not write or run the haml or erubis benchmarks. Atmos did. I don't care for haml or erubis and I don't actually care about their results. If you want to make changes, you're welcome to blog it. We can go back and forth until we have a complete list.

There's one library you missed that might be worth considering... Nokogiri. In provides a similar API to tagz, and is significantly faster in my benchmark.

http://gist.github.com/65516

brynary: that's actually really cool. I'll have to look into that tomorrow w/ Aaron. thanks.

@brynary - nokogiri absolutely kicks ass for reading, but it cannot currently generate tags like '' due to the implementation. when it does i'll have another look at it too. just fyi.

-a

@zenspider - what's the issue with sinatra?

I applaud your distaste for overly-complex plugins, but I think your use of wc was a wee bit too cavalier. Erector has one file, unicode.rb, that's automatically generated. It contains over 18000 lines like this:

Erector::CHARACTERS = {
  :space => 0x0020,
  :exclamation_mark => 0x0021,
  :quotation_mark => 0x0022,

This is a performance optimization so we can do wicked fast entity escaping via a single method called 'character' and shouldn't really count towards any reasonable complexity metric. Without that file it weighs in at a svelte 1053 lines, plus 247 for Rails support and 96 for an optional table widget that nobody's really using anyway. We also have copious rdoc documentation so the actual LOC is much lower.

And it's fast too! I just added it to that benchmark and it's 10% faster than Tagz for the simple case, which doesn't count our optimizations around partials (we make sure to keep using the same output stream wherever possible to minimize string copy or realloc).

http://gist.github.com/115155

Tell me what you like most about Tagz. Is it the "include Tagz.globally" feature? Is it the ability to emit invalid HTML? Chances are we either do it already in Erector or it'll be trivial to add.

Here's my latest run from http://gist.github.com/115155 , adding loops and variables (so HAML doesn't get to cheat by just caching a static string).

# of iterations = 10000
                          user     system      total        real
tagz                 13.740000   0.070000  13.810000 ( 14.064954)
erector               6.500000   0.050000   6.550000 (  6.744592)
erector_mixin         6.390000   0.040000   6.430000 (  6.508313)
nokogiri              5.950000   0.040000   5.990000 (  6.137619)
builder              17.810000   0.090000  17.900000 ( 18.166922)
haml                  5.080000   0.030000   5.110000 (  5.175749)
erubis                0.400000   0.010000   0.410000 (  0.411160)
markaby              27.620000   0.130000  27.750000 ( 28.234580)

Bottom line: Erubis is hella fast! And Markaby and Builder are slow. The rest are about the same. I wonder what tagz is doing that's making it drag behind the rest of the middle of the pack...

Leave a comment

About this Entry

This page contains a single entry by zenspider published on February 16, 2009 12:19 AM.

heckle version 1.4.2 has been released! was the previous entry in this blog.

rubyforge version 1.0.3 has been released! is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Pages

Powered by Movable Type 4.32-en