On 10/26/06, Phrogz <gavin / refinery.com> wrote:
> Rick DeNatale wrote:
> > (int & 1).zero?  is probably faster than
> > int[0].zero?
> >
> > Since the latter generates the mask using the bit position.
> >
> > and both should be faster than
> >
> > (int % 2).zero?
> >
> > But it it's performance critical, benchmarking is recommended to
> > verify. One should never simply take 'rules of thumb' for granted.
>
> Let's just do it rather than speculating:
>
> require 'benchmark'
>
> ITERATIONS = 1_000_000
> MAX_INT    = 2 ** 30
> NUMS = (1..ITERATIONS).map{ rand(MAX_INT) }
> Benchmark.bmbm{ |x|
>   x.report '(n % 2).zero?' do
>     NUMS.each{ |n|
>       (n % 2).zero?
>     }
>   end
>
>   x.report 'n[0].zero?' do
>     NUMS.each{ |n|
>       n[0].zero?
>     }
>   end
>
>   x.report '(n & 1).zero?' do
>     NUMS.each{ |n|
>       (n & 1).zero?
>     }
>   end
> }
>
> #=> Rehearsal -------------------------------------------------
> #=> (n % 2).zero?   2.060000   0.020000   2.080000 (  2.244595)
> #=> n[0].zero?      1.960000   0.010000   1.970000 (  2.145238)
> #=> (n & 1).zero?   1.990000   0.020000   2.010000 (  2.275232)
> #=> ---------------------------------------- total: 6.060000sec
> #=>
> #=>                     user     system      total        real
> #=> (n % 2).zero?   2.070000   0.010000   2.080000 (  2.315105)
> #=> n[0].zero?      1.960000   0.020000   1.980000 (  2.143038)
> #=> (n & 1).zero?   1.990000   0.010000   2.000000 (  2.173120)
>
> There is a very, very, very small difference in speed.

Which is why I suggested actually doing the benchmarks after
explaining why mod would be expected to be slower.

Now I took this benchmark and simplified it, taking out the zero? call
in an attempt to isolate the cost of each approach.  I also added a
benchmark of an empty loop. On my machine this seems to make the hot
run of mod FASTER than bit testing on 1.8, but not on 1.9:

rick@frodo:/public/rubyscripts$ ruby bitbm.rb
Rehearsal ----------------------------------------------
empty loop   1.840000   0.410000   2.250000 (  3.366799)
n % 2        3.680000   0.910000   4.590000 (  5.636832)
n[0]         3.650000   1.020000   4.670000 (  9.325569)
(n & 1)      3.890000   0.840000   4.730000 (  6.346543)
------------------------------------ total: 16.240000sec

                 user     system      total        real
empty loop   1.790000   0.480000   2.270000 (  3.695585)
n % 2        3.700000   0.880000   4.580000 (  8.864955)
n[0]         3.740000   0.910000   4.650000 ( 13.832782)
(n & 1)      3.850000   0.870000   4.720000 ( 12.540741)

rick@frodo:/public/rubyscripts$ ruby1.9 bitbm.rb
Rehearsal ----------------------------------------------
empty loop   1.280000   0.010000   1.290000 (  1.964290)
n % 2        2.900000   0.010000   2.910000 (  6.053066)
n[0]         2.370000   0.000000   2.370000 (  3.572928)
(n & 1)      2.660000   0.010000   2.670000 (  4.322753)
------------------------------------- total: 9.240000sec

                 user     system      total        real
empty loop   1.230000   0.000000   1.230000 (  2.415479)
n % 2        2.750000   0.010000   2.760000 (  4.028753)
n[0]         2.390000   0.000000   2.390000 (  3.322041)
(n & 1)      2.400000   0.010000   2.410000 (  3.548835)

And n[0] seems faster than n & 1 which is a slight surprise, to me at least.

By the way, is the actual meaning of user, system, total, and real
documented anywhere.  I've looked around a few times, and haven't
uncovered it.  I guess I could try to devine it by reading the code
for benchmark, but...


Here's my version of Phrogz benchmark code FWIW;
require 'benchmark'

ITERATIONS = 1_000_000
MAX_INT    = 2 ** 30
NUMS = (1..ITERATIONS).map{ rand(MAX_INT) }
Benchmark.bmbm{ |x|

 x.report 'empty loop' do
   NUMS.each {|n|}
 end

 x.report 'n % 2' do
   NUMS.each{ |n|
     n % 2
   }
 end

 x.report 'n[0]' do
   NUMS.each{ |n|
     n[0]
   }
 end

 x.report '(n & 1)' do
   NUMS.each{ |n|
     (n & 1)
   }
 end
}
-- 
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/