Подтвердить что ты не робот

Компилятор С# не оптимизирует ненужные передачи

Несколько дней назад, написав ответ на этот вопрос здесь, при переполнении, я немного удивился компилятору С#, который не делал то, что я ожидал от него делать. Посмотрите на следующие фрагменты кода:

Во-первых:

object[] array = new object[1];

for (int i = 0; i < 100000; i++)
{
    ICollection<object> col = (ICollection<object>)array;
    col.Contains(null);
}

Во-вторых:

object[] array = new object[1];

for (int i = 0; i < 100000; i++)
{
    ICollection<object> col = array;
    col.Contains(null);
}

Единственное различие в коде между двумя фрагментами - это передача в ICollection <object> . Поскольку объект [] явно реализует интерфейс ICollection <object> , я ожидал, что два фрагмента будут скомпилированы до одного и того же IL и поэтому будут идентичными. Тем не менее, при выполнении тестов производительности на них я заметил, что последний был примерно в 6 раз быстрее прежнего.

После сравнения IL из обоих фрагментов я заметил, что оба метода были идентичны, за исключением инструкции IL castclass в первом фрагменте.

Удивленный этим, я теперь задаюсь вопросом, почему компилятор С# не является "умным" здесь. Вещи никогда не такие простые, как кажется, поэтому почему компилятор С# здесь немного наивен?

4b9b3361

Ответ 1

Я предполагаю, что вы обнаружили незначительную ошибку в оптимизаторе. Для массивов есть всевозможные специальные коды. Спасибо, что привлек мое внимание.

Ответ 2

Это грубое предположение, но я думаю, что это связано с отношением Array к его универсальному IEnumerable.

В .NET Framework версии 2.0 Класс Array реализует System.Collections.Generic.IList, System.Collections.Generic.ICollection, а также System.Collections.Generic.IEnumerable общие интерфейсы. реализации предоставляются массивы во время выполнения и, следовательно, не видимый для сборки документации инструменты. В результате общий интерфейсы не отображаются в синтаксис объявления для массива класса, и нет ссылки темы для членов интерфейса, которые доступный только путем литья массива в общий тип интерфейса (явный реализация интерфейса). Ключ что нужно знать, когда вы бросаете массив к одному из этих интерфейсов члены, которые добавляют, вставляют или удалять элементы NotSupportedException.

См. Статья MSDN.

Неясно, относится ли это к .NET 2.0+, но в этом специальном случае было бы совершенно понятно, почему компилятор не может оптимизировать ваше выражение, если оно становится действительным только во время выполнения.

Ответ 3

Это не похоже на просто упущенную возможность в компиляторе подавить подачу. Он будет работать, если вы напишете это следующим образом:

    ICollection<object> col = array as ICollection<object>;

который предполагает, что он становится слишком консервативным, потому что броски могут бросать исключения. Тем не менее, он работает, когда вы бросаете не-общий ICollection. Я бы сделал вывод, что они просто упустили это.

Там больше проблема оптимизации при работе здесь, JIT-компилятор не применяет оптимизацию подъема цикла. Он должен был переписать код следующим образом:

object[] array = new object[1];
ICollection<object> col = (ICollection<object>)array;
for (int i = 0; i < 100000; i++)
{
    col.Contains(null);
}

Какая стандартная оптимизация в генераторе кода C/С++, например. Тем не менее, оптимизатор JIT не может записывать много циклов в виде анализа, необходимого для обнаружения таких возможных оптимизаций. Радость в этом заключается в том, что оптимизированный управляемый код по-прежнему довольно отлаживаемый. И что все еще есть роль для программиста С# для написания кода исполнения.