代码之家  ›  专栏  ›  技术社区  ›  Stefan Monov

我如何学习足够多的关于clr的知识来对性能问题做出有根据的猜测?

  •  10
  • Stefan Monov  · 技术社区  · 15 年前

    是的,我 使用分析器(ants)。但在微观层面上,它不能告诉你如何解决你的问题。我现在正处于微观优化阶段。例如,我在分析:

    for (int x = 0; x < Width; x++)
    {
        for (int y = 0; y < Height; y++)
        {
            packedCells.Add(Data[x, y].HasCar);
            packedCells.Add(Data[x, y].RoadState);
            packedCells.Add(Data[x, y].Population);
        }
    }
    

    蚂蚁表明,Y环线需要花费很多时间。我认为这是因为它必须不断地称之为“高度吸纳者”。所以我创建了一个本地 int height = Height; 在循环之前,检查内部循环 y < height . 这真的让表演更糟了!蚂蚁现在告诉我X环线是个问题。呵呵?这应该是无关紧要的,它是外环!

    最终我得到了一个启示——也许使用一个属性作为外部循环绑定和一个本地的内部循环绑定使得clr经常在一个“局部”缓存和一个“这个指针”缓存之间跳转(我习惯用CPU缓存来思考)。所以我也做了一个局部的宽度,并修复了它。

    从那里,很明显我也应该为数据做一个本地的-即使数据甚至不是一个 财产 (那是一块地)。事实上,这给我带来了更多的表演。

    然而,令人困惑的是,重新排序X和Y循环(以提高缓存使用率)却没有任何区别,即使数组很大(30000x3000)。

    现在,我想学习 为什么 我所做的改进了性能。 你建议我读什么书?

    7 回复  |  直到 15 年前
        1
  •  10
  •   Peter Mortensen Pieter Jan Bonestroo    15 年前

    CLR via C# 作者:杰弗里·里克特。

    这是一本好书,有人把它和 C# in depth .

        2
  •  4
  •   Hans Passant    15 年前

    这里根本不涉及clr,应该将其转换为直接的机器代码,而不需要调用clr。JIT编译器负责生成该机器代码,它有一个优化器,试图找出最有效的代码。它有局限性,不能在上面花费大量的时间。

    它做的一件重要的事情是找出应该在CPU寄存器中存储哪些局部变量。当您将height属性放在局部变量中时,它发生了变化。它可能决定将该变量存储在寄存器中。但是现在有一个不太适合存储另一个变量。就像x或y变量一样,它对速度至关重要。是的,那会减慢速度。

    你的外环诊断不好。这可能是由于JIT优化器重新安排了循环代码,使得探查器很难将机器代码映射回相应的C语句。

    类似地,优化器可能已经决定了您使用数组的效率低下,并将索引顺序切换回原来的顺序。不太确定它是否真的做到了这一点,但并非不可能。

    总之,在这里,您可以了解的唯一方法是查看生成的机器代码。关于x86汇编代码有很多不错的书,尽管这些天可能有点难找到。您的起点是debug+windows+反汇编。

    但是请记住,即使是机器代码也不能很好地预测代码的运行效率。现代CPU核心非常复杂,机器代码不再代表核心内部实际发生的事情。唯一的尝试和真正的方法是你已经做了:尝试和错误。

        3
  •  3
  •   ChrisW    15 年前

    阿尔宾-不。老实说,我不认为在一个探查器外跑步会改变性能差异,所以我不费心。你认为我应该?你以前有问题吗?(我正在编译优化)

    在调试程序下运行会改变性能:当它在调试程序下运行时,实时编译器会自动禁用优化(使调试更容易)!

    如果必须这样做,请使用调试器附加到已经运行的已JITTED进程。

        4
  •  2
  •   Henk Holterman    15 年前

    关于使用数组,您应该知道的一件事是,clr将 总是 确保数组索引不超出界限。它对一维数组进行了优化,但对二维数组没有优化。

    知道了这一点,你可能想要基准 MyCell Data[][] 而不是 MyCell Data[,]

        5
  •  1
  •   paul_71    15 年前

    嗯,我不认为循环注册是真正的问题。 1。我尽量避免访问数组 Data 每个内环三次。 2。我也建议你重新考虑一下这三个问题 Add 语句:显然您要访问一个集合三次以添加一些琐碎的数据。每次迭代只能访问一次,并添加包含三个条目的数据类型:

    for (int y = 0; ... {
     tTemp = Data[x, y];
     packedCells.Add(new {
      tTemp.HasCar, tTemp.RoadState, tTemp.Population 
     });
    }
    

    另一种外观显示,您基本上是通过将矩阵复制到数组(或其他一些序列集合)来矢量化矩阵…这是必要的吗?为什么不定义一个专门的索引器来模拟线性访问呢?更好的是,如果您只需要枚举条目(在该示例中,不需要随机访问),为什么不使用适当的LINQ表达式呢?

        6
  •  1
  •   Community Mohan Dere    9 年前

    要点1)有根据的猜测不是进行性能调整的方法。在这种情况下,我可以和大多数人一样猜测,但猜测是错误的做法。

    第2点)在你知道他们实际上在告诉你什么之前,你需要很好地理解他们。 Here's a discussion of the issues. 例如,许多分析人员所做的就是告诉您“程序在哪里花费时间”,即 程序计数器 花时间,所以它们几乎完全不了解函数调用请求的时间,这就是您的内部循环似乎包含的内容。

    我做了很多性能调整,下面是我要做的。我在两个活动之间循环:

    • 总时间测量。这不需要特殊工具。我不想测量个别的程序。

    • “瓶颈”位置。这不需要以任何速度运行代码,因为我 不测量 . 我要做的是 定位 负责相当大百分比时间的代码行。我知道它们是哪一行,因为它们在该百分比的堆栈中,堆栈示例很容易找到它们。

    一旦我找到了一个“瓶颈”并解决了它,我就回到第一步,测量我节省了多少时间,然后在下一个“瓶颈”上再做一遍,通常是2到6次。“放大效应”对我有帮助,其中固定问题放大了剩余问题使用的百分比。它既适用于宏观优化,也适用于微观优化。

    (很抱歉,如果我不能写没有引号的“瓶颈”,因为我认为我从未发现过类似瓶颈的性能问题。相反,他们都只是做一些不需要做的事情。)

        7
  •  0
  •   Paul Michalik    15 年前

    由于评论可能会被监督,我重复自己:优化代码本身就太过繁琐了。您根本不需要显式地线性化您的矩阵,请参见上面的注释:定义一个实现 IEnumerable<MyCell> 把它输入格式化程序。

    当我试图添加另一个答案时,会收到一个警告,因此我将回收此答案。:)在阅读了史蒂夫的评论并思考了一会儿之后,我建议如下: 如果序列化多维数组太慢(还没有尝试过,我只是相信你…)就不要使用它!看来,您的矩阵不是稀疏的,并且有固定的维度。因此,使用索引器定义将单元格保持为简单线性数组的结构:

    [Serializable()]
    class CellMatrix {
     Cell [] mCells;
    
     public int Rows { get; }
     public int Columns { get; }
    
     public Cell this (int i, int j) {
      get {
       return mCells[i + Rows * j];    
      }   
      // setter...
     }
    
     // constructor taking rows/cols...
    }
    

    像这样的事情应该像本机数组一样快速序列化…我不建议硬编码的布局 Cell 为了在那里保存几个字节…

    干杯, 保罗