[Python-checkins] benchmarks: Port PyPy's raytrace benchmark.

brett.cannon python-checkins at python.org
Sat Sep 15 00:12:35 CEST 2012


http://hg.python.org/benchmarks/rev/ba088479cdda
changeset:   180:ba088479cdda
user:        Brett Cannon <brett at python.org>
date:        Fri Sep 14 15:54:09 2012 -0400
summary:
  Port PyPy's raytrace benchmark.

files:
  perf.py                    |   12 +-
  performance/bm_raytrace.py |  378 +++++++++++++++++++++++++
  2 files changed, 388 insertions(+), 2 deletions(-)


diff --git a/perf.py b/perf.py
--- a/perf.py
+++ b/perf.py
@@ -1693,6 +1693,14 @@
     return SimpleBenchmark(MeasureHexiom2, *args, **kwargs)
 
 
+def MeasureRaytrace(python, options):
+    bm_path = Relative("performance/bm_raytrace.py")
+    return MeasureGeneric(python, options, bm_path, iteration_scaling=1)
+
+def BM_Raytrace(*args, **kwargs):
+    return SimpleBenchmark(MeasureRaytrace, *args, **kwargs)
+
+
 def MeasureLogging(python, options, extra_args):
     """Test the performance of Python's logging module.
 
@@ -2083,8 +2091,8 @@
                 "2n3": ["calls", "chaos", "fannkuch", "fastpickle",
                         "fastunpickle", "go", "hexiom2", "json_dump_v2",
                         "json_load", "math", "logging", "meteor_contest",
-                        "normal_startup", "nqueens", "pathlib", "regex",
-                        "spectral_norm", "startup_nosite", "richards",
+                        "normal_startup", "nqueens", "pathlib", "raytrace",
+                        "regex", "richards", "spectral_norm", "startup_nosite", 
                         "threading", "unpack_sequence"],
                 # After 2to3-conversion
                 "py3k": ["2to3", "2n3", "mako_v2"]
diff --git a/performance/bm_raytrace.py b/performance/bm_raytrace.py
new file mode 100644
--- /dev/null
+++ b/performance/bm_raytrace.py
@@ -0,0 +1,378 @@
+# This file contains definitions for a simple raytracer.
+# Copyright Callum and Tony Garnock-Jones, 2008.
+# This file may be freely redistributed under the MIT license,
+# http://www.opensource.org/licenses/mit-license.php
+
+# from http://www.lshift.net/blog/2008/10/29/toy-raytracer-in-python
+from __future__ import with_statement
+
+import math
+
+EPSILON = 0.00001
+
+class Vector(object):
+    def __init__(self, initx, inity, initz):
+        self.x = initx
+        self.y = inity
+        self.z = initz
+
+    def __str__(self):
+        return '(%s,%s,%s)' % (self.x, self.y, self.z)
+
+    def __repr__(self):
+        return 'Vector(%s,%s,%s)' % (self.x, self.y, self.z)
+
+    def magnitude(self):
+        return math.sqrt(self.dot(self))
+
+    def __add__(self, other):
+        if other.isPoint():
+            return Point(self.x + other.x, self.y + other.y, self.z + other.z)
+        else:
+            return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
+
+    def __sub__(self, other):
+        other.mustBeVector()
+        return Vector(self.x - other.x, self.y - other.y, self.z - other.z)
+
+    def scale(self, factor):
+        return Vector(factor * self.x, factor * self.y, factor * self.z)
+
+    def dot(self, other):
+        other.mustBeVector()
+        return (self.x * other.x) + (self.y * other.y) + (self.z * other.z)
+
+    def cross(self, other):
+        other.mustBeVector()
+        return Vector(self.y * other.z - self.z * other.y,
+                      self.z * other.x - self.x * other.z,
+                      self.x * other.y - self.y * other.x)
+
+    def normalized(self):
+        return self.scale(1.0 / self.magnitude())
+
+    def negated(self):
+        return self.scale(-1)
+
+    def __eq__(self, other):
+        return (self.x == other.x) and (self.y == other.y) and (self.z == other.z)
+
+    def isVector(self):
+        return True
+
+    def isPoint(self):
+        return False
+
+    def mustBeVector(self):
+        return self
+
+    def mustBePoint(self):
+        raise 'Vectors are not points!'
+
+    def reflectThrough(self, normal):
+        d = normal.scale(self.dot(normal))
+        return self - d.scale(2)
+
+Vector.ZERO = Vector(0,0,0)
+Vector.RIGHT = Vector(1,0,0)
+Vector.UP = Vector(0,1,0)
+Vector.OUT = Vector(0,0,1)
+
+assert Vector.RIGHT.reflectThrough(Vector.UP) == Vector.RIGHT
+assert Vector(-1,-1,0).reflectThrough(Vector.UP) == Vector(-1,1,0)
+
+class Point(object):
+    def __init__(self, initx, inity, initz):
+        self.x = initx
+        self.y = inity
+        self.z = initz
+
+    def __str__(self):
+        return '(%s,%s,%s)' % (self.x, self.y, self.z)
+
+    def __repr__(self):
+        return 'Point(%s,%s,%s)' % (self.x, self.y, self.z)
+
+    def __add__(self, other):
+        other.mustBeVector()
+        return Point(self.x + other.x, self.y + other.y, self.z + other.z)
+
+    def __sub__(self, other):
+        if other.isPoint():
+            return Vector(self.x - other.x, self.y - other.y, self.z - other.z)
+        else:
+            return Point(self.x - other.x, self.y - other.y, self.z - other.z)
+
+    def isVector(self):
+        return False
+
+    def isPoint(self):
+        return True
+
+    def mustBeVector(self):
+        raise 'Points are not vectors!'
+
+    def mustBePoint(self):
+        return self
+
+class Sphere(object):
+    def __init__(self, centre, radius):
+        centre.mustBePoint()
+        self.centre = centre
+        self.radius = radius
+
+    def __repr__(self):
+        return 'Sphere(%s,%s)' % (repr(self.centre), self.radius)
+
+    def intersectionTime(self, ray):
+        cp = self.centre - ray.point
+        v = cp.dot(ray.vector)
+        discriminant = (self.radius * self.radius) - (cp.dot(cp) - v*v)
+        if discriminant < 0:
+            return None
+        else:
+            return v - math.sqrt(discriminant)
+
+    def normalAt(self, p):
+        return (p - self.centre).normalized()
+
+class Halfspace(object):
+    def __init__(self, point, normal):
+        self.point = point
+        self.normal = normal.normalized()
+
+    def __repr__(self):
+        return 'Halfspace(%s,%s)' % (repr(self.point), repr(self.normal))
+
+    def intersectionTime(self, ray):
+        v = ray.vector.dot(self.normal)
+        if v:
+            return 1 / -v
+        else:
+            return None
+
+    def normalAt(self, p):
+        return self.normal
+
+class Ray(object):
+    def __init__(self, point, vector):
+        self.point = point
+        self.vector = vector.normalized()
+
+    def __repr__(self):
+        return 'Ray(%s,%s)' % (repr(self.point), repr(self.vector))
+
+    def pointAtTime(self, t):
+        return self.point + self.vector.scale(t)
+
+Point.ZERO = Point(0,0,0)
+
+a = Vector(3,4,12)
+b = Vector(1,1,1)
+
+class PpmCanvas(object):
+    def __init__(self, width, height, filenameBase):
+        import array
+        self.bytes = array.array('B', [0] * (width * height * 3))
+        for i in range(width * height):
+            self.bytes[i * 3 + 2] = 255
+        self.width = width
+        self.height = height
+        self.filenameBase = filenameBase
+
+    def plot(self, x, y, r, g, b):
+        i = ((self.height - y - 1) * self.width + x) * 3
+        self.bytes[i  ] = max(0, min(255, int(r * 255)))
+        self.bytes[i+1] = max(0, min(255, int(g * 255)))
+        self.bytes[i+2] = max(0, min(255, int(b * 255)))
+
+    def save(self):
+        pass
+        #with open(self.filenameBase + '.ppm', 'wb') as f:
+        #    f.write('P6 %d %d 255\n' % (self.width, self.height))
+        #    f.write(self.bytes.tostring())
+
+def firstIntersection(intersections):
+    result = None
+    for i in intersections:
+        candidateT = i[1]
+        if candidateT is not None and candidateT > -EPSILON:
+            if result is None or candidateT < result[1]:
+                result = i
+    return result
+
+class Scene(object):
+    def __init__(self):
+        self.objects = []
+        self.lightPoints = []
+        self.position = Point(0, 1.8, 10)
+        self.lookingAt = Point.ZERO
+        self.fieldOfView = 45
+        self.recursionDepth = 0
+
+    def moveTo(self, p):
+        self.position = p
+
+    def lookAt(self, p):
+        self.lookingAt = p
+
+    def addObject(self, object, surface):
+        self.objects.append((object, surface))
+
+    def addLight(self, p):
+        self.lightPoints.append(p)
+
+    def render(self, canvas):
+        #print 'Computing field of view'
+        fovRadians = math.pi * (self.fieldOfView / 2.0) / 180.0
+        halfWidth = math.tan(fovRadians)
+        halfHeight = 0.75 * halfWidth
+        width = halfWidth * 2
+        height = halfHeight * 2
+        pixelWidth = width / (canvas.width - 1)
+        pixelHeight = height / (canvas.height - 1)
+
+        eye = Ray(self.position, self.lookingAt - self.position)
+        vpRight = eye.vector.cross(Vector.UP).normalized()
+        vpUp = vpRight.cross(eye.vector).normalized()
+
+        #print 'Looping over pixels'
+        previousfraction = 0
+        for y in range(canvas.height):
+            currentfraction = float(y) / canvas.height
+            if currentfraction - previousfraction > 0.05:
+                canvas.save()
+                #print '%d%% complete' % (currentfraction * 100)
+                previousfraction = currentfraction
+            for x in range(canvas.width):
+                xcomp = vpRight.scale(x * pixelWidth - halfWidth)
+                ycomp = vpUp.scale(y * pixelHeight - halfHeight)
+                ray = Ray(eye.point, eye.vector + xcomp + ycomp)
+                colour = self.rayColour(ray)
+                canvas.plot(x,y,*colour)
+
+        #print 'Complete.'
+
+    def rayColour(self, ray):
+        if self.recursionDepth > 3:
+            return (0,0,0)
+        try:
+            self.recursionDepth = self.recursionDepth + 1
+            intersections = [(o, o.intersectionTime(ray), s) for (o, s) in self.objects]
+            i = firstIntersection(intersections)
+            if i is None:
+                return (0,0,0) ## the background colour
+            else:
+                (o, t, s) = i
+                p = ray.pointAtTime(t)
+                return s.colourAt(self, ray, p, o.normalAt(p))
+        finally:
+            self.recursionDepth = self.recursionDepth - 1
+
+    def _lightIsVisible(self, l, p):
+        for (o, s) in self.objects:
+            t = o.intersectionTime(Ray(p,l - p))
+            if t is not None and t > EPSILON:
+                return False
+        return True
+
+    def visibleLights(self, p):
+        result = []
+        for l in self.lightPoints:
+            if self._lightIsVisible(l, p):
+                result.append(l)
+        return result
+
+def addColours(a, scale, b):
+    return (a[0] + scale * b[0],
+            a[1] + scale * b[1],
+            a[2] + scale * b[2])
+
+class SimpleSurface(object):
+    def __init__(self, **kwargs):
+        self.baseColour = kwargs.get('baseColour', (1,1,1))
+        self.specularCoefficient = kwargs.get('specularCoefficient', 0.2)
+        self.lambertCoefficient = kwargs.get('lambertCoefficient', 0.6)
+        self.ambientCoefficient = 1.0 - self.specularCoefficient - self.lambertCoefficient
+
+    def baseColourAt(self, p):
+        return self.baseColour
+
+    def colourAt(self, scene, ray, p, normal):
+        b = self.baseColourAt(p)
+
+        c = (0,0,0)
+        if self.specularCoefficient > 0:
+            reflectedRay = Ray(p, ray.vector.reflectThrough(normal))
+            #print p, normal, ray.vector, reflectedRay.vector
+            reflectedColour = scene.rayColour(reflectedRay)
+            c = addColours(c, self.specularCoefficient, reflectedColour)
+
+        if self.lambertCoefficient > 0:
+            lambertAmount = 0
+            for lightPoint in scene.visibleLights(p):
+                contribution = (lightPoint - p).normalized().dot(normal)
+                if contribution > 0:
+                    lambertAmount = lambertAmount + contribution
+            lambertAmount = min(1,lambertAmount)
+            c = addColours(c, self.lambertCoefficient * lambertAmount, b)
+
+        if self.ambientCoefficient > 0:
+            c = addColours(c, self.ambientCoefficient, b)
+
+        return c
+
+class CheckerboardSurface(SimpleSurface):
+    def __init__(self, **kwargs):
+        SimpleSurface.__init__(self, **kwargs)
+        self.otherColour = kwargs.get('otherColour', (0,0,0))
+        self.checkSize = kwargs.get('checkSize', 1)
+
+    def baseColourAt(self, p):
+        v = p - Point.ZERO
+        v.scale(1.0 / self.checkSize)
+        if (int(abs(v.x) + 0.5) + \
+            int(abs(v.y) + 0.5) + \
+            int(abs(v.z) + 0.5)) \
+           % 2:
+            return self.otherColour
+        else:
+            return self.baseColour
+
+def _main():
+    Canvas = PpmCanvas
+    c = Canvas(100,100,'test_raytrace')
+    #c = Canvas(640,480,'test_raytrace_big')
+    s = Scene()
+    s.addLight(Point(30, 30, 10))
+    s.addLight(Point(-10, 100, 30))
+    s.lookAt(Point(0, 3, 0))
+    s.addObject(Sphere(Point(1,3,-10), 2), SimpleSurface(baseColour = (1,1,0)))
+    for y in range(6):
+        s.addObject(Sphere(Point(-3 - y * 0.4, 2.3, -5), 0.4),
+                    SimpleSurface(baseColour = (y / 6.0, 1 - y / 6.0, 0.5)))
+    s.addObject(Halfspace(Point(0,0,0), Vector.UP), CheckerboardSurface())
+    s.render(c)
+
+def main(n):
+    import time
+    times = []
+    for i in range(5):
+        _main() # warmup
+    for i in range(n):
+        t1 = time.time()
+        _main()
+        t2 = time.time()
+        times.append(t2 - t1)
+    return times
+
+if __name__ == "__main__":
+    import util, optparse
+    parser = optparse.OptionParser(
+        usage="%prog [options]",
+        description="Test the performance of the raytrace benchmark")
+    util.add_standard_options_to(parser)
+    options, args = parser.parse_args()
+
+    util.run_benchmark(options, options.num_runs, main)
+

-- 
Repository URL: http://hg.python.org/benchmarks


More information about the Python-checkins mailing list