[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