几年前,我在学校接到了一个任务,在那里我必须并行化一个 Raytracer。
这是一个简单的任务,我真的很喜欢做它。
今天,我想对光线追踪器进行分析,看看我是否能让它运行得更快(无需彻底检查代码)。在分析过程中,我注意到一些有趣的事情:
// Sphere.Intersect
public bool Intersect(Ray ray, Intersection hit)
{
double a = ray.Dir.x * ray.Dir.x +
ray.Dir.y * ray.Dir.y +
ray.Dir.z * ray.Dir.z;
double b = 2 * (ray.Dir.x * (ray.Pos.x - Center.x) +
ray.Dir.y * (ray.Pos.y - Center.y) +
ray.Dir.z * (ray.Pos.z - Center.z));
double c = (ray.Pos.x - Center.x) * (ray.Pos.x - Center.x) +
(ray.Pos.y - Center.y) * (ray.Pos.y - Center.y) +
(ray.Pos.z - Center.z) * (ray.Pos.z - Center.z) - Radius * Radius;
// more stuff here
}
根据分析器,25% 的 CPU 时间花在get_Dir
和上get_Pos
,这就是为什么我决定按以下方式优化代码:
// Sphere.Intersect
public bool Intersect(Ray ray, Intersection hit)
{
Vector3d dir = ray.Dir, pos = ray.Pos;
double xDir = dir.x, yDir = dir.y, zDir = dir.z,
xPos = pos.x, yPos = pos.y, zPos = pos.z,
xCen = Center.x, yCen = Center.y, zCen = Center.z;
double a = xDir * xDir +
yDir * yDir +
zDir * zDir;
double b = 2 * (xDir * (xPos - xCen) +
yDir * (yPos - yCen) +
zDir * (zPos - zCen));
double c = (xPos - xCen) * (xPos - xCen) +
(yPos - yCen) * (yPos - yCen) +
(zPos - zCen) * (zPos - zCen) - Radius * Radius;
// more stuff here
}
以惊人的结果。
在原始代码中,使用其默认参数运行光线追踪器(创建一个 1024x1024 图像,只有直射闪电且没有 AA)将需要大约 88 秒。
在修改后的代码中,同样需要不到60 秒。
只需对代码进行一点修改,我就实现了约 1.5 的加速。
起初,我认为 getterRay.Dir
是Ray.Pos
在幕后做一些事情,这会减慢程序的速度。
以下是两者的吸气剂:
public Vector3d Pos
{
get { return _pos; }
}
public Vector3d Dir
{
get { return _dir; }
}
所以,两者都返回一个 Vector3D,就是这样。
我真的很想知道,与直接访问变量相比,调用 getter 会花费更长的时间。
是因为 CPU 缓存变量吗?或者可能重复调用这些方法的开销加起来?或者也许 JIT 处理后一种情况比前者更好?或者也许还有其他我没有看到的东西?
任何见解将不胜感激。
编辑:
正如@MatthewWatson 建议的那样,我使用了StopWatch
在调试器之外的时间发布版本。为了消除噪音,我多次运行测试。结果,前一个代码需要大约 21 秒(在 20.7 和 20.9 之间)才能完成,而后者只需要大约 19 秒(在 19 和 19.2 之间)。
差异已经可以忽略不计,但仍然存在。