3

我一直在查看 mscorlib 以查看通用集合如何优化其枚举器,我偶然发现了这一点:

// in List<T>.Enumerator<T>
public bool MoveNext()
{
    List<T> list = this.list;
    if ((this.version == list._version) && (this.index < list._size))
    {
        this.current = list._items[this.index];
        this.index++;
        return true;
    }
    return this.MoveNextRare();
}

堆栈大小为 3,字节码大小应为 80 字节。该MoveNextRare方法的命名让我大吃一惊,它包含一个错误案例和一个空集合案例,因此显然这违反了关注点分离。

我假设该MoveNext方法以这种方式拆分以优化堆栈空间并帮助 JIT,我想对我的一些性能瓶颈做同样的事情,但是没有硬数据,我不希望我的巫毒编程变成货物-邪教;)

谢谢!弗洛里安

4

2 回答 2

3

如果您要考虑List<T>.Enumerator为了性能而“奇怪”的方式,请首先考虑这一点:它是一个可变结构。随时恐惧地退缩;我知道我知道。

最终,如果没有基准测试/分析它们在您的特定应用程序中产生的差异,我不会开始模仿 BCL 的优化。它可能适合 BCL,但不适合您;不要忘记 BCL 在安装时会通过整个类似 NGEN 的服务。找出适合您的应用程序的唯一方法是对其进行测量。

你说你想为你的性能瓶颈尝试同样的事情:这表明你已经知道瓶颈,这表明你已经进行了某种测量。因此,尝试这种优化并对其进行测量,然后看看性能的提升是否值得随之而来的可读性/维护的痛苦。

尝试某件事并对其进行衡量,然后根据该证据做出决定,这并没有什么大不了的。

于 2010-02-17T10:09:06.230 回答
1

将其分成两个功能有一些优点:

如果要内联方法,则仅内联快速路径,错误处理仍将是函数调用。这可以防止内联花费过多的额外空间。但是 80 字节的 IL 可能仍然高于内联的阈值(它曾经被记录为 32 字节,不知道自 .NET 2.0 以来是否已更改)。

即使没有内联,该函数也会更小,更容易放入 CPU 的指令缓存中,并且由于慢速路径是独立的,因此不必在每次快速路径时都将其提取到缓存中。

它可以帮助 CPU 分支预测器优化更常见的路径(返回 true)。

我认为 MoveNextRare 总是会返回 false,但是通过像这样构造它,它变成了一个尾调用,如果它是私有的并且只能从这里调用,那么 JIT 理论上可以在这两种方法之间建立一个自定义调用约定,包括只是一条 jmp 指令,没有序言,也没有重复的尾声。

于 2010-02-19T22:29:00.630 回答