几年前,我在学校接到一项作业,必须并行化光线追踪器。
这是一项简单的任务,我真的很喜欢做它。
今天,我想对光线追踪器进行分析,看看是否可以让它运行得更快(无需完全修改代码)。在分析过程中,我注意到一些有趣的事情:
// 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
and 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
}
取得了惊人的结果。
在原始代码中,使用默认参数运行光线追踪器(创建仅使用直接闪电且没有 AA 的 1024x1024 图像)将需要〜88秒.
在修改后的代码中,同样需要比60秒.
只需对代码进行一点修改,我就实现了约 1.5 的加速。
起初,我以为吸气剂是Ray.Dir
and Ray.Pos
我们在幕后做一些事情,这会减慢程序的速度。
以下是两者的吸气剂:
public Vector3d Pos
{
get { return _pos; }
}
public Vector3d Dir
{
get { return _dir; }
}
所以,两者都返回一个 Vector3D,仅此而已。
我真的很想知道,调用 getter 怎么会比直接访问变量花费更长的时间。
是因为CPU缓存变量吗?或者重复调用这些方法的开销可能会增加?或者也许 JIT 处理后一种情况比前一种情况更好?或者也许还有其他我没有看到的东西?
任何见解将不胜感激。
Edit:
正如@MatthewWatson 所建议的,我使用了StopWatch
在调试器之外计时发布版本。为了消除噪音,我多次进行了测试。结果,前面的代码需要〜21秒(20.7 和 20.9 之间)完成,而后者仅〜19秒(19 和 19.2 之间)。
差异已经变得可以忽略不计,但仍然存在。