Зачем использовать модификатор параметра "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, который вы передаете только ссылку на структуру
но попытка изменить значение объекта дает ошибку компилятора.
И да, это позволит вам оптимизировать производительность кода, если вы используете большие структуры.