Насколько размер байт-кода влияет на JIT / Inlining / Performance? - PullRequest
3 голосов
/ 17 февраля 2010

Я копался в 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, и я хотел бы сделать то же самое для некоторых из моих узких мест перфорации, но без жестких данных я не хочу, чтобы мои программирование вуду превращается в груз-культ;)

Спасибо! Florian

Ответы [ 2 ]

3 голосов
/ 17 февраля 2010

Если вы собираетесь подумать о том, как List<T>.Enumerator является «нечетным» для повышения производительности, сначала подумайте об этом: это изменяемая структура . Не стесняйтесь отшатываться с ужасом; Я знаю, что знаю.

В конечном счете, я бы не стал подражать оптимизациям из BCL, не сравнив и не проанализировав, какую разницу они имеют в вашем конкретном приложении. Это вполне может подойти для BCL, но не для вас; не забывайте, что BCL проходит весь сервис, аналогичный NGEN, при установке. Единственный способ узнать, что подходит для вашего приложения, - это измерить его.

Вы говорите, что хотите попробовать то же самое для своих узких мест в производительности: это предполагает, что вы уже знаете узкие места, что предполагает, что у вас есть какое-то измерение на месте. Итак, попробуйте эту оптимизацию и измерите ее, а затем посмотрите, стоит ли выигрыш в производительности для боли читаемости / обслуживания, которое сопровождает ее.

Нет ничего сложного в том, чтобы что-то попробовать и измерить, а затем принимать решения на основе этих доказательств.

1 голос
/ 20 февраля 2010

Разделение на две функции имеет ряд преимуществ:

Если бы метод был встроен, то был бы встроен только быстрый путь, и обработка ошибок все равно была бы вызовом функции. Это предотвращает встраивание слишком большого дополнительного пространства. Но 80 байтов IL, вероятно, все еще выше порогового значения для вставки (когда-то это было задокументировано как 32 байта, не знаю, изменилось ли оно после .NET 2.0).

Даже если она не встроена, функция будет меньше по размеру и будет легче помещаться в кэш инструкций ЦП, а поскольку медленный путь разделен, его не нужно будет извлекать в кэш каждый раз, когда быстрый путь .

Это может помочь предиктору ветвления ЦП оптимизировать для более общего пути (возвращая true).

Я думаю, что MoveNextRare всегда будет возвращать false, но, структурируя его таким образом, он становится хвостовым вызовом, и если он является частным и может быть вызван только отсюда, то JIT теоретически может создать собственное соглашение о вызовах между этими двумя методы, состоящие только из инструкции jmp без пролога и без дублирования эпилога.

...