Rubystats 0.2.3 released

Posted by Bryan on July 06, 2008

Thanks to Franz Schwartau who noticed a bug in the Rubystats gem, and after four hours of debugging, I’ve released Rubystats 0.2.3 which fixes the bug* Franz found in the beta distribution calculations.

Thanks again, Franz.

 * By bug I mean bugs, and by bugs I mean stupid mistakes in my code and lack of sufficient test coverage.  But hey, this was some of the first ruby code I wrote (in fact I learned a lot of the ruby syntax in this project because I ported it from the PHPMath library), so I’m not surprised.  Not to say that the Rubystats library is junk.. it’s worked flawlessly for me for a few years now and has quite a bit of test coverage (more than PHPMath does — I implemented the tests from PHPMath and then some), but it’s certainly not perfect yet. It’s great to have members of the community finding bugs and letting me know about them.  That’s what open source is all about I guess.

Heel RubyGem

Posted by Bryan on April 25, 2008

I recently came across the Heel RubyGem. To quote the RDoc page, “Heel is a small static web server for use when you need a quick web server for a directory. Once the server is running, heel will use launchy to open your browser at the URL of your document root.” It uses Thin as a webserver

It’s just nice to, for example, launch Heel in your system’s rubygems directory so you can easily browse around. You can view ruby files with syntax highlighting (using coderay). Here’s a sample ruby file.. as you can see, it’s nicely done:

heel image

I was just looking the Thin webserver and came across this. It’s been handy so far.

Writing tests for acts_as plugins

Posted by Bryan on February 20, 2008

It took me a while to figure out a good way to approach this, so thought I’d share. I’ve been wondering how to best write test code for a Rails plugin that deals with ActiveRecord models, and after trying a few different approaches, I decided to do the thing I should have done in the first place: look at the test code for the plugins in Rails core. The acts_as_tree test code is what I ended up using as a guide for my tests for MultiFieldDate. This approach uses SQLite as an in-memory database, so you do have to have SQLite3 and the sqlite3-ruby gem installed for this to work. Alternatively, this should work with other database engines, but it would probably be a good idea to use table prefixes in those cases to avoid conflicts with existing tables.

Here’s a snippet of the MultiFieldDate test code:

require 'test/unit'
require 'rubygems'
require 'active_record'
$:.unshift File.dirname(__FILE__) + '/../lib'
require File.dirname(__FILE__) + '/../init'
 
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
 
# AR keeps printing annoying schema statements
$stdout = StringIO.new
 
def setup_db
  ActiveRecord::Base.logger
  ActiveRecord::Schema.define(:version => 1) do
    create_table :people do |t|
      t.column :name, :string
      t.column :birth_date, :date
      t.column :birth_year, :integer
      t.column :birth_month, :integer
      t.column :birth_day, :integer
    end
  end
end
 
def teardown_db
  ActiveRecord::Base.connection.tables.each do |table|
    ActiveRecord::Base.connection.drop_table(table)
  end
end
 
class Person < ActiveRecord::Base
  multi_field_date :birth, :date_field => 'birth_date', :year_field => 'birth_year',
  :month_field => 'birth_month', :day_field => 'birth_day'
end
 
class PersonTest < Test::Unit::TestCase
 
  def setup
    setup_db
  end
 
  def teardown
    teardown_db
  end
 
  def test_create_without_birth_date
    person = Person.new(:name => 'Joe Jones')
    person.save
    assert(person.valid?, true)
  end
#...
end

Upgrading to Rails 2.0 2

Posted by Bryan on February 16, 2008

Here’s a couple one-liner scripts to help convert your Rails app to 2.0. These scripts are by no means perfect, but should help if you have a lot of files with “start_form_tag”, “end_form_tag”, or “:post => true”. Back your files up first, of course.

find . |grep -i "\.erb$" | xargs ruby -p -i -e "gsub(/<%=.*start_form_tag(.*)%>/i, '<% form_tag \1 do -%>')"
find . |grep -i "\.erb$" | xargs ruby -p -i -e "gsub(/<%.*end_form_tag.*%>/i, '<% end #form_tag-%>')"
find . |grep -i "\.erb$" | xargs ruby -p -i -e 'gsub(/:post\s=>\strue/i, ":method\s=>\s:post")'

Replace “erb” with “rtml” if you haven’t converted your views to .erb yet.

Responses to benchmarks

Posted by Bryan on February 06, 2008

A couple people posted responses to the benchmarks on my Sun blog, so here they are.

From Susan Potter:

Bryan,

I wrote a benchmarking blog post in October last year relating to the difference in code-level optimizations between Ruby 1.8.6 and Ruby 1.9.0 and how the roles were reversed in these two versions: (link)

And her direct response:

This is a response to Bryan Donovan’s blog post called ways to pass options.

Bryan’s original benchmark was run on Ruby 1.8.4. I will be running the benchmarks on Ruby 1.8.6 and Ruby 1.9 to demonstrate performance reversal of code level optimizations between Ruby versions (as I did in Ruby performance reversal benchmark 1.8 to 1.9).
I will use Bryan’s Ruby benchmark code to run for both Ruby 1.8.6 and Ruby 1.9.0 (official).

Ruby 1.8.6 Results

Below are the results of running the options passing benchmark in Ruby 1.8.6:

With no option values passed
Rehearsal -------------------------------------------
delete:   0.290000   0.050000   0.340000 (  0.428564)
merge:    0.700000   0.190000   0.890000 (  1.017415)
or_nil:   0.370000   0.050000   0.420000 (  0.509209)
---------------------------------- total: 1.650000sec
 
              user     system      total        real
delete:   0.300000   0.050000   0.350000 (  0.428401)
merge:    0.760000   0.130000   0.890000 (  1.010474)
or_nil:   0.360000   0.050000   0.410000 (  0.490170)
 
With some option values passed
Rehearsal -------------------------------------------
delete:   0.360000   0.060000   0.420000 (  0.505094)
merge:    0.890000   0.130000   1.020000 (  1.150165)
or_nil:   0.390000   0.070000   0.460000 (  0.563713)
---------------------------------- total: 1.900000sec
 
              user     system      total        real
delete:   0.340000   0.060000   0.400000 (  0.489191)
merge:    0.920000   0.110000   1.030000 (  1.149708)
or_nil:   0.350000   0.090000   0.440000 (  0.528287)
 
With all option values passed
Rehearsal -------------------------------------------
delete:   0.420000   0.050000   0.470000 (  0.547446)
merge:    0.970000   0.130000   1.100000 (  1.228093)
or_nil:   0.420000   0.050000   0.470000 (  0.574593)
---------------------------------- total: 2.040000sec
 
              user     system      total        real
delete:   0.400000   0.050000   0.450000 (  0.485888)
merge:    0.960000   0.140000   1.100000 (  1.237509)
or_nil:   0.400000   0.060000   0.460000 (  0.536063)

Ruby 1.9.0 Results

Below are the results of running the options passing benchmark in Ruby 1.9.0:

With no option values passed
Rehearsal -------------------------------------------
delete:   0.150000   0.000000   0.150000 (  0.230391)
merge:    0.540000   0.010000   0.550000 (  0.645735)
or_nil:   0.120000   0.000000   0.120000 (  0.188468)
---------------------------------- total: 0.820000sec
 
              user     system      total        real
delete:   0.160000   0.000000   0.160000 (  0.238828)
merge:    0.520000   0.000000   0.520000 (  0.610940)
or_nil:   0.120000   0.000000   0.120000 (  0.180744)
 
With some option values passed
Rehearsal -------------------------------------------
delete:   0.220000   0.000000   0.220000 (  0.294110)
merge:    0.690000   0.010000   0.700000 (  0.803382)
or_nil:   0.220000   0.000000   0.220000 (  0.297869)
---------------------------------- total: 1.140000sec
 
              user     system      total        real
delete:   0.210000   0.000000   0.210000 (  0.283983)
merge:    0.700000   0.000000   0.700000 (  0.802766)
or_nil:   0.230000   0.000000   0.230000 (  0.295433)
 
With all option values passed
Rehearsal -------------------------------------------
delete:   0.270000   0.010000   0.280000 (  0.348052)
merge:    0.770000   0.040000   0.810000 (  0.947252)
or_nil:   0.240000   0.010000   0.250000 (  0.336790)
---------------------------------- total: 1.340000sec
 
              user     system      total        real
delete:   0.280000   0.000000   0.280000 (  0.660810)
merge:    0.750000   0.000000   0.750000 (  1.618925)
or_nil:   0.240000   0.000000   0.240000 (  0.478159)

In Ruby 1.8.6 the delete code is consistently more efficient, whereas in Ruby 1.9.0 the or_nil code is mostly more efficient, except for the With some option values passed scenario.

This once again shows that when optimizing performance on the code level, you need to be careful that you justify your micro-optimizations before you create code spaghetti just “because….”. Readability and maintainability is most important and when needed, only then should you optimize.

Charles Oliver Nutter’s Response
Charles Oliver Nutter (of JRuby) responded as well:

Stumbled on your blog post and ran the same numbers in JRuby on soylatte Java 6 on basically the same machine. My numbers for Ruby 1.8.6p111 were roughly the same as yours.

Here’s Ruby:

 user system total real
delete: 0.200000 0.000000 0.200000 ( 0.207049)
merge: 0.710000 0.010000 0.720000 ( 0.720514)
or_nil: 0.260000 0.000000 0.260000 ( 0.256742)
 
With some option values passed
 
user system total real
delete: 0.250000 0.000000 0.250000 ( 0.260502)
merge: 0.810000 0.010000 0.820000 ( 0.818434)
or_nil: 0.290000 0.000000 0.290000 ( 0.294220)
 
With all option values passed
 
user system total real
delete: 0.320000 0.000000 0.320000 ( 0.319335)
merge: 0.890000 0.010000 0.900000 ( 0.896330)
or_nil: 0.330000 0.000000 0.330000 ( 0.336673)

And here’s JRuby:

With no option values passed
 
user system total real
delete: 0.133000 0.000000 0.133000 ( 0.133000)
merge: 0.318000 0.000000 0.318000 ( 0.319000)
or_nil: 0.474000 0.000000 0.474000 ( 0.474000)
 
With some option values passed
 
user system total real
delete: 0.170000 0.000000 0.170000 ( 0.169000)
merge: 0.295000 0.000000 0.295000 ( 0.295000)
or_nil: 0.145000 0.000000 0.145000 ( 0.146000)
 
With all option values passed
 
user system total real
delete: 0.154000 0.000000 0.154000 ( 0.154000)
merge: 0.289000 0.000000 0.289000 ( 0.288000)
or_nil: 0.101000 0.000000 0.101000 ( 0.100000)

Looks like something’s artificially slowing down the “or_nil” case in the first scenario, but the rest of the numbers look pretty solid.

Interesting results all around. I’m not going to lose sleep over the milliseconds I’m saving/losing with various approaches, so I’ll likely use the ops = ops[:foo] || ‘default’ approach for readability.

Ruby: benchmarking ways to pass options to a method 2

Posted by Bryan on February 05, 2008

(reposting from my Sun blog)

I’ve wondered before what the fastest way is to pass a hash of options to a method in Ruby.. so today I benchmarked a few methods I’ve used in the past.

I’ve seen three main ways of passing an options hash to a method and extracting the options or use default values if they weren’t passed in:

  • Merge the options with a hash of defaults, then assign values to local variables (or just use the options hash directly within the method)
  • Use something like var = options[:var] || ‘default’
  • Use the delete method on the hash, e.g., var = options.delete(:var) || ‘default’

From what I can tell, using delete is the fastest:

require 'benchmark'
 
def ops_delete(ops={})
  a = ops.delete(:a) || 11
  b = ops.delete(:b) || 22
  c = ops.delete(:c) || 33
  d = ops.delete(:d) || 44
end
 
def ops_merge(ops={})
  ops = {:a => 11, :b => 22, :c => 33, :d => 44}.merge(ops)
  a = ops[:a]
  b = ops[:b]
  c = ops[:c]
  d = ops[:d]
end
 
def ops_or(ops={})
  a = ops[:a] || 11
  b = ops[:b] || 22
  c = ops[:c] || 33
  d = ops[:d] || 44
end
 
 
n = 100000
puts "With no option values passed"
Benchmark.bmbm(7) do |x|
  x.report("delete:") { n.times do ops_delete; end }
  x.report("merge:")  { n.times do ops_merge;  end }
  x.report("ops_or:") { n.times do ops_or;     end }
end
 
puts
puts "With some option values passed"
Benchmark.bmbm(7) do |x|
  x.report("delete:") { n.times do ops_delete(:a => 5, :c => 2); end }
  x.report("merge:")  { n.times do ops_merge(:a => 5, :c => 2);  end }
  x.report("ops_or:") { n.times do ops_or(:a => 5, :c => 2);     end }
end
 
puts
puts "With all option values passed"
Benchmark.bmbm(7) do |x|
  x.report("delete:") { n.times do ops_delete(:a => 5, :b => 1, :c => 2, :d => 4); end }
  x.report("merge:")  { n.times do ops_merge(:a => 5, :b => 1, :c => 2, :d => 4);  end }
  x.report("ops_or:") { n.times do ops_or(:a => 5, :b => 1, :c => 2, :d => 4);     end }
end

Results (rehearsals omitted):

With no option values passed
              user     system      total        real
delete:   0.210000   0.000000   0.210000 (  0.209877)
merge:    0.720000   0.000000   0.720000 (  0.750394)
ops_or:   0.260000   0.000000   0.260000 (  0.258416)
 
With some option values passed
              user     system      total        real
delete:   0.250000   0.000000   0.250000 (  0.255536)
merge:    0.830000   0.000000   0.830000 (  0.831358)
ops_or:   0.310000   0.000000   0.310000 (  0.314737)
 
With all option values passed
              user     system      total        real
delete:   0.310000   0.000000   0.310000 (  0.306889)
merge:    0.900000   0.000000   0.900000 (  0.905261)
ops_or:   0.340000   0.000000   0.340000 (  0.348061)

This was done in Ruby 1.8.4 on a 2GHz MacBook Intel Core Duo with 1GB 667 MHz DDR2 SDRAM.