C# 为什么一个简单的get语句这么慢?
几年前,我在学校接受了一项作业,在那里我必须将光线跟踪器并行化。C# 为什么一个简单的get语句这么慢?,c#,getter,C#,Getter,几年前,我在学校接受了一项作业,在那里我必须将光线跟踪器并行化。 这是一项简单的任务,我真的很喜欢做它 今天,我想分析一下raytracer,看看是否可以让它运行得更快(而不需要彻底修改代码)。在分析过程中,我注意到了一些有趣的事情: // Sphere.Intersect public bool Intersect(Ray ray, Intersection hit) { double a = ray.Dir.x * ray.Dir.x +
这是一项简单的任务,我真的很喜欢做它 今天,我想分析一下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
}
结果惊人
在原始代码中,使用其默认参数运行光线跟踪器(创建一个只有direct lightning且没有AA的1024x1024图像)将花费~88秒在修改后的代码中,同样的操作只需要不到60秒的时间 只需对代码进行一点修改,我就实现了~1.5的加速 起初,我认为
Ray.Dir
和Ray.Pos
的getter在幕后做了一些事情,这会减慢程序的速度
以下是这两种方法的要点:
public Vector3d Pos
{
get { return _pos; }
}
public Vector3d Dir
{
get { return _dir; }
}
所以,两者都返回一个向量3D,就是这样
我真的很想知道,调用getter比直接访问变量要花那么长的时间
是因为CPU缓存变量吗?或者反复调用这些方法的开销加起来了?或者JIT处理后一种情况比前者更好?或者也许还有什么我看不到的
如有任何见解,将不胜感激
编辑:
正如@matthewatson所建议的,我使用了一个StopWatch
在调试器之外定时发布构建。为了消除噪音,我运行了多次测试。因此,前一个代码需要~21秒(介于20.7和20.9之间)才能完成,而后一个代码只需要~19秒(介于19和19.2之间)。差异已经变得微不足道,但仍然存在。导言 我敢打赌,由于C#中涉及类型结构属性的一个怪癖,原始代码的速度要慢得多。这并不完全是直观的,但这种类型的属性本质上是缓慢的。为什么?因为结构不是通过引用传递的。因此,为了访问
ray.Dir.x
,您必须
ray
get_Dir
并将结果存储在临时变量中。这涉及复制整个结构,即使只使用了字段“x”x
Dir
和Pos
都只调用一次;进一步访问这些值仅包括上述第三步:
x
在调试模式下,将跳过类似的优化以提供更好的去贝格体验。即使在发布模式下,您也会发现大多数的不安通常不会这样做。我不知道确切的原因,但我相信这是因为字段并不总是单词对齐的。现代CPU有奇怪的性能要求。:-) 您是否使用秒表来计时从任何调试器外部运行的发布版本?(如果不是,你应该!)发布版本应该内联简单的getter,但调试版本不会。@MatthewWatson我确实使用秒表,但88秒和60秒来自分析器
Analyze->Start Performance Analysis Alt+F2
->挂钟时间(秒)
使用调试版本。您必须对发布版本计时才能获得正确的结果。正如我所说,调试版本不内联简单的属性获取程序和设置程序,而发布版本则内联简单的属性获取程序和设置程序。(顺便说一句,我说的是抖动,不是IL代码。)我认为这将对这个特定代码产生巨大的影响。@MatthewWatson谢谢你的提示。我按照你的建议做了,得到了一些更一致的结果。有了优化,代码运行需要约19秒,而没有优化则需要约21秒。尽管差异可以忽略不计,但仍然存在差异。也许还可以尝试将(xPos-xCen)
(以及相关的)表达式移动到局部变量中。计算三次没有意义。编辑:如果它们是结构,则通过属性检索它们可能需要更长的时间,因为每次点击属性时,它都会将结构复制到一个全新的结构。(请记住,属性只是一个方法)