为什么简单的get语句如此之慢?

Nol*_*nar 11 c# getter

几年前,我在学校完成了一项任务,在那里我必须并行化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
    }
Run Code Online (Sandbox Code Playgroud)

根据探查,的CPU时间的25%用于get_Dirget_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
    }
Run Code Online (Sandbox Code Playgroud)

取得惊人的成果.

在原始代码中,使用默认参数运行光线跟踪器(创建仅具有直接闪电且没有AA的1024x1024图像)将花费大约88秒.
在修改后的代码中,同样需要不到60秒.
只有对代码进行了这么少的修改,我实现了~1.5的加速.

起初,我认为吸尘器正在做一些事情,Ray.Dir并且Ray.Pos正在做一些事情,这会减慢程序的速度.

以下是两者的吸气剂:

    public Vector3d Pos
    {
        get { return _pos; }
    }

    public Vector3d Dir
    {
        get { return _dir; }
    }
Run Code Online (Sandbox Code Playgroud)

所以,两者都返回一个Vector3D,就是这样.

我真的很想知道,与直接访问变量相比,调用getter会花费更长的时间.

是因为CPU缓存变量?或者也许重复调用这些方法的开销加起来了?或者JIT处理后一种情况可能比前者更好?或许还有其他我没看到的东西?

任何见解将不胜感激.

编辑:

正如@MatthewWatson建议的那样,我使用a StopWatch来在调试器之外发布版本.为了消除噪音,我多次运行测试.结果,前一个代码需要大约21秒(在20.7和20.9之间)才能完成,而后者仅需要大约19秒(在19和19.2之间).
差异已经可以忽略不计了,但它仍然存在.

abo*_*u00 7

介绍

我愿意打赌原始代码是如此之慢,因为C#中的怪癖涉及类型结构的属性.它并不完全直观,但这种类型的属性本质上很慢.为什么?因为结构不是通过引用传递的.所以为了访问ray.Dir.x,你必须这样做

  1. 加载局部变量ray.
  2. 调用get_Dir并将结果存储在临时变量中.这涉及复制整个结构,即使只使用字段'x'.
  3. x从临时副本访问字段.

查看原始代码,get访问器被调用18次.这是一个巨大的浪费,因为它意味着整个结构被复制18次.在优化的代码中,只有两个副本 - Dir并且Pos只被调用一次; 进一步访问这些值只包括上面的第三步:

  1. x从临时副本访问字段.

总结一下,结构和属性不会结合在一起.

为什么C#使用struct属性以这种方式运行?

它与C#中的结构是值类型有关.您传递值本身,而不是指向值的指针.

为什么编译器不能识别get访问器只是返回一个字段,并且完全绕过该属性?

在调试模式下,跳过这样的优化以提供更好的debegging体验.即使在发布模式下,您也会发现大多数紧张情绪通常不会这样做.我不确切知道为什么,但我相信这是因为该字段并不总是字对齐.现代CPU具有奇怪的性能要求.:-)