[Shootout-list] Faster Ruby nbody benchmark

Jabari Zakiya jzakiya@mail.com
Mon, 21 Mar 2005 01:47:04 -0500


Here is a much faster nbody Ruby benchmark.
For N=3D1_000_000 (1 million) the original
code took 41 minutes to execute on my system.
(AMD K-7, 600Mhz, 640MB, Mandrake 10.1 Ofl, Ruby 1.8.2)
This modified code executed the benchmark in 17 minutes.
The modification consists of unrolling the loops in
4 methods in the Vector3D class which do vector math.

This modification probably would speed up all the
dynamic languages (Perl, Python, PHP, etc). For C/C++
with good optimizing compilers, loop unrolling may=20
already be taking place, but it's something that could
be tested for.

The benchmark originally listed the benchmark
as having an error. However, the original code
ran on my system with no errors.

The "error" was the erroneous execution of the benchmark,
resulting from N set to 1, and not 1 million as required.

Jabari Zakiyia

###########################################
# The Computer Language Benchmark Shootout
# http://shootout.alioth.debian.org
# nbody Ruby benchmark
#
# original code by Martin DeMello
# modified by Jabari Zakiya 3/20/05

include Math

SOLAR_MASS =3D 4*PI*PI
DAYS_PER_YEAR =3D 365.24

Vector3D =3D Struct.new("Vector3D", :x, :y, :z)

class Vector3D

  def *(val)
    Vector3D.new(*self.map {|i| i * val})
  end

  def /(val)
    Vector3D.new(*self.map {|i| i / val})
  end

  #in-place add with scale
  # a.adds(b, s) -> a +=3D b*s

  def adds(other, scale)
    self[0] +=3D other[0]*scale; self[1] +=3D other[1]*scale
    self[2] +=3D other[2]*scale
  end

  def subs(other, scale)
    self[0] -=3D other[0]*scale; self[1] -=3D other[1]*scale
    self[2] -=3D other[2]*scale
  end

  def magnitude
    x=3Dself[0]; y=3Dself[1]; z=3Dself[2]
    sqrt(x*x + y*y + z*z)
  end

  # |self - other|
  def dmag(other)
    x=3Dself[0]-other[0]; y=3Dself[1]-other[1]; z=3Dself[2]-other[2]
    sqrt(x*x + y*y + z*z)
  end
end

class Planet
  attr_accessor :pos, :v, :mass

  def initialize(x, y, z, vx, vy, vz, mass)
    @pos =3D Vector3D.new(x, y, z)
    @v =3D Vector3D.new(vx, vy, vz) * DAYS_PER_YEAR
    @mass =3D mass * SOLAR_MASS
  end

  def distance(other)
    self.pos.dmag(other.pos)
  end
end

jupiter =3D Planet.new(
   4.84143144246472090e+00,
   -1.16032004402742839e+00,
   -1.03622044471123109e-01,
   1.66007664274403694e-03,
   7.69901118419740425e-03,
   -6.90460016972063023e-05,
   9.54791938424326609e-04)

saturn =3D Planet.new(
   8.34336671824457987e+00,
   4.12479856412430479e+00,
   -4.03523417114321381e-01,
   -2.76742510726862411e-03,
   4.99852801234917238e-03,
   2.30417297573763929e-05,
   2.85885980666130812e-04)

uranus =3D Planet.new(
   1.28943695621391310e+01,
   -1.51111514016986312e+01,
   -2.23307578892655734e-01,
   2.96460137564761618e-03,
   2.37847173959480950e-03,
   -2.96589568540237556e-05,
   4.36624404335156298e-05)

neptune =3D Planet.new(
   1.53796971148509165e+01,
   -2.59193146099879641e+01,
   1.79258772950371181e-01,
   2.68067772490389322e-03,
   1.62824170038242295e-03,
   -9.51592254519715870e-05,
   5.15138902046611451e-05)

sun =3D Planet.new(0, 0, 0, 0, 0, 0, 1)

class Array
  def each_pair
    a =3D []
    each_index {|i|
      ((i+1)...length).each {|j|
        yield at(i), at(j)
      }
    }
  end
end

bodies =3D [sun, jupiter, saturn, uranus, neptune]

class << bodies
  def advance(dt)
    mag =3D m1 =3D m2 =3D nil
    each_pair {|b1, b2|
      d =3D b1.distance(b2)
      mag =3D dt/(d*d*d)

      m1 =3D b1.mass * mag
      m2 =3D b2.mass * mag

      b1.v.adds(b2.pos, m2)
      b1.v.subs(b1.pos, m2)
      b2.v.adds(b1.pos, m1)
      b2.v.subs(b2.pos, m1)
    }

    each {|b| b.pos.adds(b.v, dt)}
  end

  def energy
    e =3D 0
    each {|b| e +=3D 0.5 * b.mass * (b.v.magnitude ** 2) }
    each_pair {|b1, b2| e -=3D (b1.mass * b2.mass) / b1.distance(b2) }
    e
  end

  def offset_momentum
    p =3D Vector3D.new(0,0,0)
    sun =3D self[0]
    each {|b| p.adds(b.v, b.mass) }
    sun.v.subs(p, 1.0/sun.mass)
  end
end

require 'benchmark'

bodies.offset_momentum
puts bodies.energy
Integer(ARGV[0]).times {bodies.advance(0.01)}
puts bodies.energy

--=20
___________________________________________________________
Sign-up for Ads Free at Mail.com
http://promo.mail.com/adsfreejump.htm