Ste*_*ini 13 python performance
我在python中有一个简单的光线跟踪器.渲染图像200x200需要4分钟,这对我来说绝对太过分了.我想改善这种情况.
一些要点:我为每个像素拍摄多条光线(以提供抗锯齿),每个像素总共有16条光线.200x200x16总计640000条光线.必须测试每条光线对场景中多个Sphere对象的影响.雷也是一个相当微不足道的对象
class Ray(object):
def __init__(self, origin, direction):
self.origin = numpy.array(origin)
self.direction = numpy.array(direction)
Run Code Online (Sandbox Code Playgroud)
Sphere稍微复杂一些,并且带有hit/nohit的逻辑:
class Sphere(object):
def __init__(self, center, radius, color):
self.center = numpy.array(center)
self.radius = numpy.array(radius)
self.color = color
@profile
def hit(self, ray):
temp = ray.origin - self.center
a = numpy.dot(ray.direction, ray.direction)
b = 2.0 * numpy.dot(temp, ray.direction)
c = numpy.dot(temp, temp) - self.radius * self.radius
disc = b * b - 4.0 * a * c
if (disc < 0.0):
return None
else:
e = math.sqrt(disc)
denom = 2.0 * a
t = (-b - e) / denom
if (t > 1.0e-7):
normal = (temp + t * ray.direction) / self.radius
hit_point = ray.origin + t * ray.direction
return ShadeRecord.ShadeRecord(normal=normal,
hit_point=hit_point,
parameter=t,
color=self.color)
t = (-b + e) / denom
if (t > 1.0e-7):
normal = (temp + t * ray.direction) / self.radius hit_point = ray.origin + t * ray.direction
return ShadeRecord.ShadeRecord(normal=normal,
hit_point=hit_point,
parameter=t,
color=self.color)
return None
Run Code Online (Sandbox Code Playgroud)
现在,我运行了一些分析,看起来最长的处理时间是在hit()函数中
ncalls tottime percall cumtime percall filename:lineno(function)
2560000 118.831 0.000 152.701 0.000 raytrace/objects/Sphere.py:12(hit)
1960020 42.989 0.000 42.989 0.000 {numpy.core.multiarray.array}
1 34.566 34.566 285.829 285.829 raytrace/World.py:25(render)
7680000 33.796 0.000 33.796 0.000 {numpy.core._dotblas.dot}
2560000 11.124 0.000 163.825 0.000 raytrace/World.py:63(f)
640000 10.132 0.000 189.411 0.000 raytrace/World.py:62(hit_bare_bones_object)
640023 6.556 0.000 170.388 0.000 {map}
Run Code Online (Sandbox Code Playgroud)
这并不让我感到惊讶,我希望尽可能地减少这个值.我转到行分析,结果是
Line # Hits Time Per Hit % Time Line Contents
==============================================================
12 @profile
13 def hit(self, ray):
14 2560000 27956358 10.9 19.2 temp = ray.origin - self.center
15 2560000 17944912 7.0 12.3 a = numpy.dot(ray.direction, ray.direction)
16 2560000 24132737 9.4 16.5 b = 2.0 * numpy.dot(temp, ray.direction)
17 2560000 37113811 14.5 25.4 c = numpy.dot(temp, temp) - self.radius * self.radius
18 2560000 20808930 8.1 14.3 disc = b * b - 4.0 * a * c
19
20 2560000 10963318 4.3 7.5 if (disc < 0.0):
21 2539908 5403624 2.1 3.7 return None
22 else:
23 20092 75076 3.7 0.1 e = math.sqrt(disc)
24 20092 104950 5.2 0.1 denom = 2.0 * a
25 20092 115956 5.8 0.1 t = (-b - e) / denom
26 20092 83382 4.2 0.1 if (t > 1.0e-7):
27 20092 525272 26.1 0.4 normal = (temp + t * ray.direction) / self.radius
28 20092 333879 16.6 0.2 hit_point = ray.origin + t * ray.direction
29 20092 299494 14.9 0.2 return ShadeRecord.ShadeRecord(normal=normal, hit_point=hit_point, parameter=t, color=self.color)
Run Code Online (Sandbox Code Playgroud)
因此,似乎大部分时间都花在了这段代码上:
temp = ray.origin - self.center
a = numpy.dot(ray.direction, ray.direction)
b = 2.0 * numpy.dot(temp, ray.direction)
c = numpy.dot(temp, temp) - self.radius * self.radius
disc = b * b - 4.0 * a * c
Run Code Online (Sandbox Code Playgroud)
我真的没有看到很多优化的地方.您是否知道如何在不使用C的情况下使此代码更高效?
查看代码,看起来您的主要问题是您的代码行被调用了2560000次.无论您在该代码中执行什么样的工作,这都会花费很多时间.但是,使用numpy,你可以将很多这项工作聚合成少量的numpy调用.
首先要做的是将你的光线组合成大数组.不使用具有1x3向量作为原点和方向的Ray对象,而是使用具有命中检测所需的所有光线的Nx3阵列.点击功能的顶部最终会如下所示:
temp = rays.origin - self.center
b = 2.0 * numpy.sum(temp * rays.direction,1)
c = numpy.sum(numpy.square(temp), 1) - self.radius * self.radius
disc = b * b - 4.0 * c
Run Code Online (Sandbox Code Playgroud)
对于下一部分,您可以使用
possible_hits = numpy.where(disc >= 0.0)
a = a[possible_hits]
disc = disc[possible_hits]
...
Run Code Online (Sandbox Code Playgroud)
继续只通过判别测试的值.您可以通过这种方式轻松获得数量级的性能提升.
1)光线跟踪很有趣但是如果你完全关心性能,请转储python并切换到C.不是C++,除非你是某种超级专家,只是C.
2)具有多个(20个或更多)对象的场景中的大胜利是使用空间索引来减少交叉点测试的数量.流行的选择是kD-trees,OctTrees,AABB.
3)如果你是认真的,请查看ompf.org - 这是它的资源.
4)不要和python一起询问优化问题 - 大多数人可以通过一个拥有10万个三角形的室内场景每秒拍摄1百万到2百万条光线......每个核心.
我喜欢Python和光线跟踪,但绝不会考虑将它们组合在一起.在这种情况下,正确的优化是切换语言.
使用这样的代码,您可能会受益于记住常见的子表达式,例如self.radius * self.radiusasself.radius2和1 / self.radiusas self.one_over_radius。Python 解释器的开销可能会主导这些微不足道的改进。