[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