【问题标题】:How much does bytecode size impact JIT / Inlining / Performance?字节码大小对 JIT/内联/性能有多大影响?
【发布时间】:2011-01-17 18:58:50
【问题描述】:

我一直在寻找 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,我想对我的一些性能瓶颈做同样的事情,但没有硬数据,我不想要我的巫毒编程变成了货物崇拜;)

谢谢! 弗洛里安

【问题讨论】:

    标签: c# performance optimization clr jit


    【解决方案1】:

    如果您要考虑将List&lt;T&gt;.Enumerator 设置为“奇数”以提高性能的方式,请首先考虑这一点:它是一个可变结构。随时恐惧地退缩;我知道我知道。

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

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

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

    【讨论】:

    • 我正在寻找关于 JITter 的规范,但感谢您指出可变结构!我查看了调用是如何解决的,结果发现它没有被装箱,除非 List 被引用为 IList。此外, List.Enumerator 实例不会被直接引用然后按原样传递,它们一定会被强制转换为 IEnumerator 然后装箱而不是复制,所以我们是安全的 -因为没有人使用 IEnumerator 参数,对吧?这是一个巧妙而危险的技巧,它结合了迭代器和 clr 深层魔法的一般用例。虽然不会复制它:)
    【解决方案2】:

    把它分成两个函数有一些好处:

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

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

    它可能有助于 CPU 分支预测器针对更常见的路径进行优化(返回 true)。

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

    【讨论】:

    • 没有考虑 JIT 开销。谢谢。
    猜你喜欢
    • 2013-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-06
    • 1970-01-01
    • 2021-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多