Зачем использовать модификатор параметра "in" в С#?

Итак, я (думаю, я) понимаю, что делает in модификаторе параметра. Но то, что оно делает, кажется, довольно избыточно.

Обычно, я думаю, что единственная причина, чтобы использовать ref бы изменить вызывающую переменную, которая явно запрещенный in. Так, проходя мимо in ссылке, кажется логически эквивалентна передаче по значению.

Есть ли какое-то преимущество в производительности? Я полагал, что в стороне от вещей параметр ref должен как минимум копировать физический адрес переменной, который должен быть того же размера, что и любая типичная ссылка на объект.

Итак, тогда преимущество только в больших структурах, или есть какая-то закулисная оптимизация компилятора, которая делает ее привлекательной в других местах? Если последнее, то почему я не должен делать каждый параметр ап in?

Ответы

Ответ 1

in недавно был представлен на языке С#.

in на самом деле ref readonly. Вообще говоря, существует только один случай использования, где in может быть полезным: высокие приложения эффективности работы с большим количеством больших readonly struct с.

Предполагая, что у вас есть:

readonly struct VeryLarge
{
    public readonly long Value1;   
    public readonly long Value2;

    public long Compute() { }
    // etc
}

а также

void Process(in VeryLarge value) { }

В этом случае структура VeryLarge будет передаваться по ссылке без создания защитных копий при использовании этой структуры в методе Process (например, при вызове value.Compute()), а неизменяемость структуры обеспечивается компилятором.

Обратите внимание, что прохождение не-только для чтения - struct с in модификаторе заставит компилятор создать защитную копию при вызове методов STRUCT и доступа к свойствам в Process выше способом, что негативно скажется на производительности!

Существует действительно хорошая запись в блоге MSDN, которую я рекомендую внимательно прочитать.

Если вы хотите получить более исторический фон in -introducing, вы можете прочитать это обсуждение в репозитории GitHub на языке С#.

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

Ответ 2

проходя мимо in ссылке, кажется логически эквивалентна передаче по значению.

Правильный.

Есть ли какое-то преимущество в производительности?

Да.

Я полагал, что в стороне от вещей параметр ref должен как минимум копировать физический адрес переменной, который должен быть того же размера, что и любая типичная ссылка на объект.

Нет требования, чтобы ссылка на объект и ссылка на переменную были одинакового размера, и нет требования, чтобы либо был размер машинного слова, но да, на практике оба являются 32 битами на 32 бит и 64 бит на 64-битных машинах.

То, что вы считаете "физическим адресом" связано с этим, неясно мне. В Windows мы используем виртуальные адреса, а не физические адреса в коде режима пользователя. В каких возможных обстоятельствах вы могли бы предположить, что физический адрес имеет смысл в программе на С#, мне любопытно узнать.

Также не требуется, чтобы ссылка любого вида была реализована как виртуальный адрес хранилища. Ссылки могут быть непрозрачными ручками в таблицах GC в соответствии с выполнением спецификации CLI.

является преимуществом только в больших структурах?

Снижение стоимости передачи больших структур - это мотивирующий сценарий для этой функции.

Обратите внимание, что нет никакой гарантии, что in любой программе скорость будет быстрее, и это может замедлить работу программ. Все вопросы об эффективности должны отвечать на эмпирические исследования. Очень мало оптимизаций, которые всегда выигрывают; это не оптимизация "всегда выигрыша".

есть ли какая-то закулисная оптимизация компилятора, которая делает ее привлекательной в других местах?

Компилятору и среде выполнения разрешено делать любую оптимизацию, которую они выбирают, если это не нарушает правила спецификации С#. Насколько я знаю, такая оптимизация пока не существует in параметрах, но это не исключает таких оптимизаций в будущем.

почему бы мне не включить каждый параметр?

Ну, предположим, что вы внесли параметр int вместо параметра in int. Какие издержки наложены?

  • теперь сайту вызова требуется переменная, а не значение
  • переменная не может быть зарегистрирована. Тщательно настроенная схема распределения регистров дрожала только ввергнутым в нее ключом.
  • код на узле вызова больше, потому что он должен принять ссылку на переменную и поместить ее в стек, тогда как до того, как она сможет просто нажать значение в стек вызовов
  • более крупный код означает, что некоторые короткие команды перехода могут теперь стать инструкциями с большим прыжком, так что снова код становится больше. Это оказывает влияние на все виды вещей. Кэш заполняется раньше, у джиттера больше работы, поэтому дрожание может не делать определенных оптимизаций при больших размерах кода и т.д.
  • на сайте вызываемого абонента мы превратили доступ к значению в стеке (или регистру) в указатель на указатель. Теперь этот указатель, скорее всего, будет в кеше, но все же мы теперь превратили однонаправленный доступ к значению в доступ к двум инструкциям.
  • И так далее.

Предположим, что он double и вы меняете его на in double. Опять же, теперь переменная не может быть зарегистрирована в высокопроизводительном регистре с плавающей запятой. Это не только влияет на производительность, но и изменяет поведение программы! С# разрешено выполнять арифметику с плавающей точкой с более высокой точностью, чем 64-битная, и обычно делает это только в том случае, если поплавки могут быть зарегистрированы.

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

Ответ 3

Есть. При передаче struct ключевое слово in позволяет оптимизировать, когда компилятору нужно передать указатель, не подвергая его методу изменению содержимого. Последнее имеет решающее значение - оно позволяет избежать операции копирования. На больших структурах это может сделать мир различий.

Ответ 4

Это делается из-за подхода к функциональному программированию. Одним из основных принципов является то, что функция не должна иметь побочных эффектов, что означает, что она не должна изменять значения параметров и должна возвращать некоторое значение. В С# не было способа передать structs (и тип значения), не будучи скопированным только по ссылке, которая позволяет изменять значение. В swift есть хакерский алгоритм, который копирует struct (их коллекции - это структуры BTW), пока метод начинает изменять свои значения. Люди, которые используют быстро, не все знают о копировании. Это хорошая функция С#, так как она эффективна и ясна. Если вы посмотрите, что нового вы увидите, что все больше и больше вещей делается вокруг структур и массивов в стеке. И в заявлении просто необходимо эти функции. Есть ограничения, упомянутые в других ответах, но это не так важно для понимания того, куда ведет сеть.net.

Ответ 5

в нем ссылка на чтение только в С# 7.2

это означает, что вы не передаете весь объект в стек функций, подобный файлу ref, который вы передаете только ссылку на структуру

но попытка изменить значение объекта дает ошибку компилятора.

И да, это позволит вам оптимизировать производительность кода, если вы используете большие структуры.